diff --git a/src/app.rs b/src/app.rs index 49ce678..dc90071 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::app::apps::{App, AppCommand, ICNS_ICON}; use crate::commands::Function; -use crate::config::{Config, Shelly}; +use crate::config::{Config, MainPage, Shelly}; use crate::debounce::DebouncePolicy; use crate::utils::icns_data_to_handle; use crate::{app::tile::ExtSender, clipboard::ClipBoardContentType}; @@ -82,6 +82,7 @@ pub enum Message { WriteConfig(bool), SaveRanking, LoadRanking, + ToggleFavouriteApp(String), UpdateAvailable, ResizeWindow(Id, f32), OpenWindow, @@ -125,7 +126,7 @@ pub enum SetConfigFields { SearchUrl(String), HapticFeedback(bool), ShowMenubarIcon(bool), - AutoSuggest(bool), + SetPage(MainPage), Modes(Editable<(String, String)>), Aliases(Editable<(String, String)>), SearchDirs(Editable), diff --git a/src/app/apps.rs b/src/app/apps.rs index d872a1c..73a64a0 100644 --- a/src/app/apps.rs +++ b/src/app/apps.rs @@ -211,6 +211,14 @@ impl App { } row = row.push(container(text_block).width(Fill)); + let name = self.search_name.clone(); + let theme_clone = theme.clone(); + row = row.push( + Button::new("♥️") + .on_press_with(move || Message::ToggleFavouriteApp(name.clone())) + .style(move |_, _| result_button_style(&theme_clone)), + ); + let msg = on_press.or(match self.open_command.clone() { AppCommand::Function(func) => Some(Message::RunFunction(func)), AppCommand::Message(msg) => Some(msg), diff --git a/src/app/pages/settings.rs b/src/app/pages/settings.rs index a2dd755..71742ba 100644 --- a/src/app/pages/settings.rs +++ b/src/app/pages/settings.rs @@ -6,16 +6,19 @@ use iced::widget::Slider; use iced::widget::Space; use iced::widget::TextInput; use iced::widget::checkbox; +use iced::widget::radio; use iced::widget::text_input; use crate::app::Editable; use crate::app::SetConfigBufferFields; use crate::app::SetConfigThemeFields; use crate::commands::Function; +use crate::config::MainPage; use crate::config::Shelly; use crate::styles::delete_button_style; use crate::styles::settings_add_button_style; use crate::styles::settings_checkbox_style; +use crate::styles::settings_radio_button_style; use crate::styles::settings_save_button_style; use crate::styles::settings_slider_style; use crate::styles::settings_text_input_item_style; @@ -130,16 +133,40 @@ pub fn settings_page(config: Config) -> Element<'static, Message> { ]); let theme_clone = theme.clone(); - let auto_suggest = settings_item_row([ + let auto_suggest = settings_item_column([ settings_hint_text(theme.clone(), "Suggestions on open"), - checkbox(config.auto_suggest) - .style(move |_, _| settings_checkbox_style(&theme_clone)) - .on_toggle(|input| Message::SetConfig(SetConfigFields::AutoSuggest(input))) + settings_item_row([ + radio( + "Favourites", + MainPage::Favourites, + Some(config.main_page), + |page| Message::SetConfig(SetConfigFields::SetPage(page)), + ) + .style({ + let theme_clone = theme_clone.clone(); + move |_, _| settings_radio_button_style(&theme_clone.clone()) + }) .into(), - notice_item( - theme.clone(), - "If an empty query should give you your most used actions", - ), + radio( + "Frequently Used", + MainPage::FrequentlyUsed, + Some(config.main_page), + |page| Message::SetConfig(SetConfigFields::SetPage(page)), + ) + .style({ + let theme_clone = theme_clone.clone(); + move |_, _| settings_radio_button_style(&theme_clone.clone()) + }) + .into(), + radio("Nothing", MainPage::Blank, Some(config.main_page), |page| { + Message::SetConfig(SetConfigFields::SetPage(page)) + }) + .style(move |_, _| settings_radio_button_style(&theme_clone.clone())) + .into(), + ]) + .spacing(30) + .into(), + notice_item(theme.clone(), "What an empty query should show"), ]); let theme_clone = theme.clone(); diff --git a/src/app/tile.rs b/src/app/tile.rs index 4f96eea..f842a1c 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -109,6 +109,19 @@ impl AppIndex { ranked } + fn get_favourites(&self) -> Vec { + self.by_name + .values() + .filter_map(|x| { + if x.ranking == -1 { + Some(x.to_owned()) + } else { + None + } + }) + .collect() + } + fn empty() -> AppIndex { AppIndex { by_name: HashMap::new(), diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 17e9534..03eca1d 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -189,10 +189,9 @@ pub fn view(tile: &Tile, wid: window::Id) -> Element<'_, Message> { .height(height as u32); let text = if tile.query_lc.is_empty() { - if tile.config.auto_suggest && tile.page == Page::Main { - "Frequently used".to_string() - } else { - tile.page.to_string() + match &tile.page { + Page::Main => tile.config.main_page.to_string(), + page => page.to_string(), } } else { match results_count { diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index ef73cec..8cac396 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -32,6 +32,7 @@ use crate::app::{Message, Page, tile::Tile}; use crate::calculator::Expr; use crate::commands::Function; use crate::config::Config; +use crate::config::MainPage; use crate::debounce::DebouncePolicy; use crate::quit::get_open_apps; use crate::unit_conversion; @@ -50,7 +51,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { tile.focused = true; tile.visible = true; - if tile.page == Page::Main && tile.query_lc.is_empty() && tile.config.auto_suggest { + if tile.page == Page::Main && tile.query_lc.is_empty() { window::latest() .map(|x| x.unwrap()) .map(|id| Message::SearchQueryChanged(String::new(), id)) @@ -436,6 +437,21 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { ]) } + Message::ToggleFavouriteApp(app_name) => { + let ranking = match tile.options.by_name.get(&app_name) { + None => return Task::none(), + Some(app) => { + if app.ranking == -1 { + 0 + } else { + -1 + } + } + }; + tile.options.set_ranking(&app_name, ranking); + Task::none() + } + Message::UpdateApps => { let mut new_options = get_installed_apps(tile.config.theme.show_icons); new_options.extend(tile.config.shells.iter().map(|x| x.to_app())); @@ -678,7 +694,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { SetConfigFields::SearchUrl(url) => final_config.search_url = url, SetConfigFields::PlaceHolder(placeholder) => final_config.placeholder = placeholder, - SetConfigFields::AutoSuggest(status) => final_config.auto_suggest = status, + SetConfigFields::SetPage(page) => final_config.main_page = page, SetConfigFields::DebounceDelay(delay) => final_config.debounce_delay = delay, SetConfigFields::HapticFeedback(haptic_feedback) => { final_config.haptic_feedback = haptic_feedback @@ -860,8 +876,12 @@ fn execute_query(tile: &mut Tile, id: Id) -> Task { _ => {} } - if tile.page == Page::Main && tile.query_lc.is_empty() && tile.config.auto_suggest { - tile.results = tile.frequent_results(); + if tile.page == Page::Main && tile.query_lc.is_empty() { + tile.results = match tile.config.main_page { + MainPage::FrequentlyUsed => tile.frequent_results(), + MainPage::Blank => vec![], + MainPage::Favourites => tile.options.get_favourites(), + }; return resize_for_results_count(id, tile.results.len()); } diff --git a/src/config.rs b/src/config.rs index dc93230..667fa76 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,7 @@ pub struct Config { pub toggle_hotkey: String, pub clipboard_hotkey: String, pub buffer_rules: Buffer, + pub main_page: MainPage, pub theme: Theme, pub placeholder: String, pub search_url: String, @@ -29,7 +30,6 @@ pub struct Config { pub modes: HashMap, pub aliases: HashMap, pub search_dirs: Vec, - pub auto_suggest: bool, pub log_path: String, pub debounce_delay: u64, } @@ -46,7 +46,7 @@ impl Default for Config { search_url: "https://duckduckgo.com/search?q=%s".to_string(), haptic_feedback: false, show_trayicon: true, - auto_suggest: true, + main_page: MainPage::default(), search_dirs: vec!["~".to_string()], log_path: "/tmp/rustcast.log".to_string(), modes: HashMap::new(), @@ -57,6 +57,25 @@ impl Default for Config { } } +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Eq, Copy)] +#[serde(rename_all = "lowercase")] +pub enum MainPage { + Favourites, + FrequentlyUsed, + #[default] + Blank, +} + +impl std::fmt::Display for MainPage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + MainPage::Blank => "♥️ Rustcast", + MainPage::Favourites => "Favourites", + MainPage::FrequentlyUsed => "Frequently Used", + }) + } +} + /// The settings you can set for the theme #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[serde(default)] diff --git a/src/styles.rs b/src/styles.rs index 6b5cfa3..869a77d 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -2,7 +2,7 @@ use crate::config::Theme as ConfigTheme; use iced::Shadow; use iced::border::Radius; -use iced::widget::{button, checkbox, container, scrollable, slider}; +use iced::widget::{button, checkbox, container, radio, scrollable, slider}; use iced::{Background, Border, Color, widget::text_input}; /// Helper: mix base color with white (simple “tint”) @@ -12,6 +12,7 @@ pub fn tint(mut c: Color, amount: f32) -> Color { c.b = c.b + (1.0 - c.b) * amount; c } + /// Helper: apply alpha pub fn with_alpha(mut c: Color, a: f32) -> Color { c.a = a; @@ -119,6 +120,16 @@ pub fn results_scrollbar_style(tile: &ConfigTheme) -> scrollable::Style { } } +pub fn settings_radio_button_style(theme: &ConfigTheme) -> radio::Style { + radio::Style { + background: Background::Color(Color::TRANSPARENT), + dot_color: theme.text_color(0.4), + border_width: 1., + border_color: theme.text_color(0.7), + text_color: Some(theme.text_color(1.)), + } +} + /// Each rustcast results rows style pub fn result_row_container_style(tile: &ConfigTheme, focused: bool) -> container::Style { container::Style {