diff --git a/anyrun/src/config.rs b/anyrun/src/config.rs index 8690f5c..ebf88b9 100644 --- a/anyrun/src/config.rs +++ b/anyrun/src/config.rs @@ -40,6 +40,10 @@ pub struct Config { #[config_args(skip)] #[serde(default = "Config::default_keybinds")] pub keybinds: Vec, + + #[config_args(skip)] + #[serde(default = "Config::default_mousebinds")] + pub mousebinds: Vec, } impl Config { fn default_x() -> RelativeNum { @@ -103,6 +107,19 @@ impl Config { }, ] } + + fn default_mousebinds() -> Vec { + vec![ + Mousebind { + button: MouseButton::Primary, + action: Action::Select, + }, + Mousebind { + button: MouseButton::Secondary, + action: Action::Nop, + }, + ] + } } impl Default for Config { fn default() -> Self { @@ -121,6 +138,7 @@ impl Default for Config { layer: Self::default_layer(), keyboard_mode: Self::default_keyboard_mode(), keybinds: Self::default_keybinds(), + mousebinds: Self::default_mousebinds(), } } } @@ -173,6 +191,21 @@ pub enum Action { Select, Up, Down, + Nop, +} + +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +pub enum MouseButton { + Primary, + Secondary, + Middle, + Unknown(u32), +} + +#[derive(Deserialize, Debug, Clone, Copy)] +pub struct Mousebind { + pub button: MouseButton, + pub action: Action, } #[derive(Deserialize, Clone)] diff --git a/anyrun/src/main.rs b/anyrun/src/main.rs index 20a4b81..5b11f2c 100644 --- a/anyrun/src/main.rs +++ b/anyrun/src/main.rs @@ -16,7 +16,7 @@ use relm4::prelude::*; use wl_clipboard_rs::copy; use crate::{ - config::{Action, Config, ConfigArgs, Keybind}, + config::{Action, Config, ConfigArgs, Keybind, Mousebind}, plugin_box::{PluginBox, PluginBoxInput, PluginBoxOutput, PluginMatch}, }; @@ -106,6 +106,27 @@ impl App { (i, plugin, plugin_match) }) } + + fn select_row(&self, plugin_ind: DynamicIndex, match_ind: DynamicIndex) { + // Select match row + for (i, plugin) in self.plugins.iter().enumerate() { + if i == plugin_ind.current_index() { + for (j, plugin_match) in plugin.matches.iter().enumerate() { + if j == match_ind.current_index() { + plugin + .matches + .widget() + .select_row(Option::<>k::ListBoxRow>::Some(&plugin_match.row)); + } + } + } else { + plugin + .matches + .widget() + .select_row(Option::<>k::ListBoxRow>::None); + } + } + } } #[relm4::component] @@ -377,6 +398,7 @@ impl Component for App { } } AppMsg::Action(action) => match action { + Action::Nop => {} Action::Close => { root.close(); relm4::main_application().quit(); @@ -472,6 +494,26 @@ impl Component for App { } } } + AppMsg::PluginOutput(PluginBoxOutput::MouseAction(button, match_ind, plugin_ind)) => { + // Handle binding + if let Some(Mousebind { action, .. }) = self + .config + .mousebinds + .iter() + .find(|mousebind| mousebind.button == button) + { + // Potentially select row + match action { + Action::Select | Action::Nop => self.select_row(plugin_ind, match_ind), + _ => { + // Don't select row for other actions + } + }; + + // Perform action + sender.input(AppMsg::Action(*action)); + } + } } self.update_view(widgets, sender); } diff --git a/anyrun/src/plugin_box.rs b/anyrun/src/plugin_box.rs index 5a14dd1..7fdaab0 100644 --- a/anyrun/src/plugin_box.rs +++ b/anyrun/src/plugin_box.rs @@ -6,7 +6,7 @@ use gtk::{glib, pango, prelude::*}; use gtk4 as gtk; use relm4::prelude::*; -use crate::Config; +use crate::{config::MouseButton, Config}; pub struct PluginMatch { pub content: Match, @@ -14,11 +14,16 @@ pub struct PluginMatch { config: Rc, } +#[derive(Debug)] +pub enum MatchOutput { + MouseAction(MouseButton, ::Index), +} + #[relm4::factory(pub)] impl FactoryComponent for PluginMatch { type Init = (Match, Rc); type Input = (); - type Output = (); + type Output = MatchOutput; type CommandOutput = (); type ParentWidget = gtk::ListBox; view! { @@ -26,6 +31,21 @@ impl FactoryComponent for PluginMatch { set_css_classes: &["match"], set_height_request: 32, gtk::Box { + + add_controller = gtk::GestureClick { + set_button: 0, + connect_pressed[sender, index] => move |gesture, _, _, _| { + gesture.set_state(gtk::EventSequenceState::Claimed); + let button: MouseButton = match gesture.current_button() { + gtk::gdk::BUTTON_PRIMARY => MouseButton::Primary, + gtk::gdk::BUTTON_SECONDARY => MouseButton::Secondary, + gtk::gdk::BUTTON_MIDDLE => MouseButton::Middle, + other => MouseButton::Unknown(other), + }; + sender.output(MatchOutput::MouseAction(button, index.clone())).unwrap(); + } + }, + set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, set_css_classes: &["match"], @@ -74,10 +94,10 @@ impl FactoryComponent for PluginMatch { fn init_widgets( &mut self, - _index: &Self::Index, + index: &Self::Index, root: Self::Root, _returned_widget: &::ReturnedWidget, - _sender: FactorySender, + sender: FactorySender, ) -> Self::Widgets { let widgets = view_output!(); @@ -140,6 +160,11 @@ pub enum PluginBoxInput { pub enum PluginBoxOutput { MatchesLoaded, RowSelected(::Index), + MouseAction( + MouseButton, + ::Index, + ::Index, + ), } #[relm4::factory(pub)] @@ -212,12 +237,17 @@ impl FactoryComponent for PluginBox { fn init_model( (plugin, config): Self::Init, - _index: &Self::Index, - _sender: FactorySender, + index: &Self::Index, + sender: FactorySender, ) -> Self { - let matches = FactoryVecDeque::builder() + let ind = index.clone(); + let matches = FactoryVecDeque::::builder() .launch(gtk::ListBox::default()) - .detach(); + .forward(sender.output_sender(), move |output| match output { + MatchOutput::MouseAction(button, row) => { + PluginBoxOutput::MouseAction(button, row, ind.clone()) + } + }); Self { plugin, diff --git a/examples/config.ron b/examples/config.ron index 78196f5..48a6c4b 100644 --- a/examples/config.ron +++ b/examples/config.ron @@ -2,7 +2,7 @@ Config( // Position/size fields use an enum for the value, it can be either: // Absolute(n): The absolute value in pixels // Fraction(n): A fraction of the width or height of the full screen (depends on exclusive zones and the settings related to them) window respectively - + // The horizontal position, adjusted so that Relative(0.5) always centers the runner x: Fraction(0.5), @@ -15,18 +15,18 @@ Config( // The minimum height of the runner, the runner will expand to fit all the entries // NOTE: If this is set to 0, the window will never shrink after being expanded height: Absolute(1), - - // Hide match and plugin info icons - hide_icons: false, - // ignore exclusive zones, f.e. Waybar - ignore_exclusive_zones: false, + // Hide match and plugin info icons + hide_icons: false, + + // ignore exclusive zones, f.e. Waybar + ignore_exclusive_zones: false, + + // Layer shell layer: Background, Bottom, Top, Overlay + layer: Overlay, - // Layer shell layer: Background, Bottom, Top, Overlay - layer: Overlay, - // Hide the plugin info panel - hide_plugin_info: false, + hide_plugin_info: false, // Close window when a click outside the main box is received close_on_click: false, @@ -36,7 +36,7 @@ Config( // Limit amount of entries shown in total max_entries: None, - + // List of plugins to be loaded by default, can be specified with a relative path to be loaded from the // `/plugins` directory or with an absolute path to just load the file the path points to. // @@ -52,19 +52,30 @@ Config( keybinds: [ Keybind( key: "Return", - action: Select, + action: Select, ), Keybind( key: "Up", - action: Up, + action: Up, ), Keybind( key: "Down", - action: Down, + action: Down, ), Keybind( key: "Escape", - action: Close, + action: Close, + ), + ], + + mousebinds: [ + Mousebind( + button: Primary, + action: Select, + ), + Mousebind( + button: Secondary, + action: Nop, ), ], ) diff --git a/nix/modules/home-manager.nix b/nix/modules/home-manager.nix index d5d4c9d..2c341ce 100644 --- a/nix/modules/home-manager.nix +++ b/nix/modules/home-manager.nix @@ -224,6 +224,7 @@ in "select" "up" "down" + "nop" ]; }; }; @@ -232,6 +233,34 @@ in default = null; }; + mousebinds = mkOption { + type = nullOr ( + listOf (submodule { + options = { + button = mkOption { + type = str; + description = '' + Mouse button name + + Primary/Secondary/Middle or Unknown() with key code for the mouse button. + ''; + }; + action = mkOption { + type = enum [ + "close" + "select" + "up" + "down" + "nop" + ]; + }; + }; + }) + ); + default = null; + }; + + extraLines = mkOption { type = nullOr lines; default = null; @@ -328,6 +357,22 @@ in '') cfg.config.keybinds }], ''; + + mousebinds = + if cfg.config.mousebinds == null then + "" + else + '' + mousebinds: [ + ${ + concatMapStringsSep "\n" (x: '' + Mousebind( + button: ${capitalize x.button}, + action: ${capitalize x.action}, + ), + '') cfg.config.mousebinds + }], + ''; in { assertions = [ @@ -382,6 +427,7 @@ in plugins: ${toJSON parsedPlugins}, ${optionalString (cfg.config.extraLines != null) cfg.config.extraLines} ${keybinds} + ${mousebinds} ) ''; }