Run cargo format

This commit is contained in:
Armin Friedl 2022-03-04 19:53:02 +01:00
parent 3b7bff5bd7
commit a34a6a2ed4
20 changed files with 625 additions and 392 deletions

View file

@ -1,6 +1,6 @@
# Rofi Then Light # Rofi Then Light
> In the beginning there was Rofi. Rofi was Fast. And Fast was Rofi. > In the beginning there was Rofi. Rofi was good.
> >
> Then Light. > Then Light.

View file

@ -6,9 +6,22 @@ log_level = "trace"
"t" = "terminal" "t" = "terminal"
[sources.primary] [sources.primary]
matcher = "Prefix" matcher = "Flex"
source = "Windows" source = "Windows"
[sources.additional] [sources.additional]
matcher = "Flex" matcher = "Flex"
source = ["Apps", "Shell", "Test"] source = ["Apps", "Shell", "Test"]
[theme]
font = ["Sans Regular", 13]
border = 2
divider = 3
[theme.color_scheme]
base = [0.13, 0.05, 0.23, 0.9]
border = [0.23, 0.05, 0.11, 1.0]
highlight = [0.12, 0.04, 0.08, 0.9]
divider = [0.23, 0.05, 0.11, 1.0]
text = [0.87, 0.95, 0.77, 1.0]
text_highlight = [0.6, 0.8, 0.4, 1.0]

View file

@ -1,6 +1,6 @@
pub mod shared;
pub mod roftl; pub mod roftl;
pub mod settings; pub mod settings;
pub mod shared;
use super::matcher; use super::matcher;
use super::sources; use super::sources;

View file

@ -1,15 +1,14 @@
use std::collections::HashMap;
use std::sync::RwLock;
use std::sync::Arc;
use log::debug; use log::debug;
use rayon::prelude::*; use rayon::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use super::matcher::PrefixMatcher; use super::matcher::PrefixMatcher;
use super::shared::Entry;
use super::shared::Matcher; use super::shared::Matcher;
use super::shared::Source; use super::shared::Source;
use super::shared::Entry;
pub type SourceRef = Box<dyn Source>; pub type SourceRef = Box<dyn Source>;
pub type MatcherRef = Box<dyn Matcher>; pub type MatcherRef = Box<dyn Matcher>;
@ -26,7 +25,7 @@ pub struct Roftl {
primary_matcher: MatcherRef, primary_matcher: MatcherRef,
entries: Vec<Entry>, entries: Vec<Entry>,
narrow_map: Vec<usize>, narrow_map: Vec<usize>,
completions: HashMap<String, String> completions: HashMap<String, String>,
} }
impl Roftl { impl Roftl {
@ -39,18 +38,23 @@ impl Roftl {
primary_matcher: PrefixMatcher::new(), primary_matcher: PrefixMatcher::new(),
entries: vec![], entries: vec![],
narrow_map: vec![], narrow_map: vec![],
completions: HashMap::default() completions: HashMap::default(),
} }
} }
pub fn with_primary_source(mut self, source: SourceRef) -> Self { pub fn with_primary_source(mut self, source: SourceRef) -> Self {
self.primary_source = Some(source); self.primary_source = Some(source);
self self
} }
pub fn add_source(self, source: SourceRef) -> Self { pub fn add_source(self, source: SourceRef) -> Self {
if self.sources.read().unwrap().par_iter().any(|s| s.name().eq(source.name())) { if self
.sources
.read()
.unwrap()
.par_iter()
.any(|s| s.name().eq(source.name()))
{
panic! {"Source with name '{}' already exists", source.name()} panic! {"Source with name '{}' already exists", source.name()}
} }
@ -79,7 +83,8 @@ impl Roftl {
} }
fn source_primary(&mut self) { fn source_primary(&mut self) {
self.entries = self.primary_source self.entries = self
.primary_source
.par_iter_mut() .par_iter_mut()
.flat_map(|s| s.entries()) .flat_map(|s| s.entries())
.collect(); .collect();
@ -93,12 +98,18 @@ impl Roftl {
let sources = self.sources.clone(); let sources = self.sources.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let entries: Vec<Entry> = sources.write().unwrap() let entries: Vec<Entry> = sources
.par_iter_mut() .write()
.flat_map(|s| s.entries()) .unwrap()
.collect(); .par_iter_mut()
.flat_map(|s| s.entries())
.collect();
debug!("Sourced {} additional entries from {} sources", entries.len(), sources.read().unwrap().len()); debug!(
"Sourced {} additional entries from {} sources",
entries.len(),
sources.read().unwrap().len()
);
tx.send(entries).unwrap(); tx.send(entries).unwrap();
}); });
} }
@ -110,7 +121,7 @@ impl Roftl {
debug!("Processing {} additional entries", entries.len()); debug!("Processing {} additional entries", entries.len());
self.entries.append(&mut entries); self.entries.append(&mut entries);
} else { } else {
debug!("No additional entries for processing (yet)"); debug!("No additional entries for processing (yet)");
} }
} }
@ -118,20 +129,26 @@ impl Roftl {
// and not self is captured by the filter_map lambda // and not self is captured by the filter_map lambda
let completions = &self.completions; let completions = &self.completions;
let primary_matcher = &self.primary_matcher; let primary_matcher = &self.primary_matcher;
let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self.entries let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self
.entries
.par_iter() .par_iter()
.enumerate() .enumerate()
.filter_map(|(idx, entry)| { .filter_map(|(idx, entry)| {
if entry.source != "window" { return None } if entry.source != "window" {
return None;
}
if input.is_empty() { return Some((0.0, idx, entry, vec![])) } if input.is_empty() {
return Some((0.0, idx, entry, vec![]));
}
let input = completions.get(input) let input = completions
.get(input)
.map(|s| s.as_str()) // &String -> &str .map(|s| s.as_str()) // &String -> &str
.unwrap_or(input); .unwrap_or(input);
let match_result = primary_matcher.try_match(&entry.name, input); let match_result = primary_matcher.try_match(&entry.name, input);
return match_result.map(|(score, indices)| (score, idx, entry, indices) ) return match_result.map(|(score, indices)| (score, idx, entry, indices));
}) })
.collect(); .collect();
@ -141,20 +158,22 @@ impl Roftl {
let input = input.strip_prefix(",").unwrap_or(input); let input = input.strip_prefix(",").unwrap_or(input);
let matcher = &self.matcher; let matcher = &self.matcher;
scored_entries = self.entries scored_entries = self
.entries
.par_iter() .par_iter()
.enumerate() .enumerate()
.filter_map(|(idx, entry)| { .filter_map(|(idx, entry)| {
if input.is_empty() { return Some((0.0, idx, entry, vec![])) } if input.is_empty() {
return Some((0.0, idx, entry, vec![]));
}
let match_result = matcher.try_match(&entry.name, input); let match_result = matcher.try_match(&entry.name, input);
return match_result.map(|(score, indices)| (score, idx, entry, indices) ) return match_result.map(|(score, indices)| (score, idx, entry, indices));
}) })
.collect(); .collect();
} }
scored_entries scored_entries.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
self.narrow_map.clear(); self.narrow_map.clear();
for se in &scored_entries { for se in &scored_entries {
@ -171,7 +190,13 @@ impl Roftl {
let (entry, source) = self.find_selection(selection_id); let (entry, source) = self.find_selection(selection_id);
match source { match source {
usize::MAX => self.primary_source.as_ref().unwrap().actions(entry), usize::MAX => self.primary_source.as_ref().unwrap().actions(entry),
_ => self.sources.read().unwrap().get(source).unwrap().actions(entry) _ => self
.sources
.read()
.unwrap()
.get(source)
.unwrap()
.actions(entry),
} }
} }
@ -179,23 +204,38 @@ impl Roftl {
let (entry, source) = self.find_selection(selection_id); let (entry, source) = self.find_selection(selection_id);
match source { match source {
usize::MAX => self.primary_source.as_ref().unwrap().exec_default(entry), usize::MAX => self.primary_source.as_ref().unwrap().exec_default(entry),
_ => self.sources.read().unwrap().get(source).unwrap().exec_default(entry) _ => self
.sources
.read()
.unwrap()
.get(source)
.unwrap()
.exec_default(entry),
} }
} }
pub fn exec_action(&self, selection_id: usize, action: u8) { pub fn exec_action(&self, selection_id: usize, action: u8) {
let (entry, source) = self.find_selection(selection_id); let (entry, source) = self.find_selection(selection_id);
match source { match source {
usize::MAX => self.primary_source.as_ref().unwrap().exec_action(entry, action), usize::MAX => self
_ => self.sources.read().unwrap().get(source).unwrap().exec_action(entry, action) .primary_source
.as_ref()
.unwrap()
.exec_action(entry, action),
_ => self
.sources
.read()
.unwrap()
.get(source)
.unwrap()
.exec_action(entry, action),
} }
} }
fn find_selection(&self, selection_id: usize) -> (&Entry, usize) fn find_selection(&self, selection_id: usize) -> (&Entry, usize) {
{
let entry_id = self.narrow_map[selection_id]; let entry_id = self.narrow_map[selection_id];
let entry = &self.entries[entry_id]; let entry = &self.entries[entry_id];
debug!{"Got entry {:?} for id {} ", entry, entry_id}; debug! {"Got entry {:?} for id {} ", entry, entry_id};
match self.primary_source { match self.primary_source {
Some(ref s) if s.name().eq(entry.source) => return (entry, usize::MAX), Some(ref s) if s.name().eq(entry.source) => return (entry, usize::MAX),

View file

@ -1,17 +1,37 @@
use std::collections::HashMap;
use config::ConfigError;
use config::Config; use config::Config;
use config::ConfigError;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
pub struct Color(f64, f64, f64, f64);
#[derive(Debug, Deserialize)]
pub struct ColorScheme {
base: Color,
border: Color,
highlight: Color,
divider: Color,
text: Color,
text_highlight: Color,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Theme { pub struct Theme {
color_scheme: ColorScheme,
font: (String, i32),
border: f64, border: f64,
divider: f64 divider: f64,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub enum Matcher { pub enum Matcher {
Prefix, Fuse, Flex, Skim Prefix,
Fuse,
Flex,
Skim,
} }
impl Matcher { impl Matcher {
@ -20,14 +40,17 @@ impl Matcher {
Matcher::Prefix => super::matcher::PrefixMatcher::new(), Matcher::Prefix => super::matcher::PrefixMatcher::new(),
Matcher::Fuse => super::matcher::FuseMatcher::new(), Matcher::Fuse => super::matcher::FuseMatcher::new(),
Matcher::Flex => super::matcher::FlxMatcher::new(), Matcher::Flex => super::matcher::FlxMatcher::new(),
Matcher::Skim => super::matcher::SkimMatcher::new() Matcher::Skim => super::matcher::SkimMatcher::new(),
} }
} }
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub enum Source { pub enum Source {
Apps, Shell, Windows, Test Apps,
Shell,
Windows,
Test,
} }
impl Source { impl Source {
@ -36,7 +59,7 @@ impl Source {
Source::Apps => super::sources::Apps::new(), Source::Apps => super::sources::Apps::new(),
Source::Shell => super::sources::ShellHost::new(), Source::Shell => super::sources::ShellHost::new(),
Source::Windows => super::sources::Window::new(), Source::Windows => super::sources::Window::new(),
Source::Test => super::sources::TestSource::new("ts1") Source::Test => super::sources::TestSource::new("ts1"),
} }
} }
} }
@ -44,7 +67,7 @@ impl Source {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct SourceConfig<T> { pub struct SourceConfig<T> {
matcher: Matcher, matcher: Matcher,
source: T source: T,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -56,16 +79,20 @@ pub struct Sources {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase", remote = "log::Level")] #[serde(rename_all = "lowercase", remote = "log::Level")]
enum LogLevelDef { enum LogLevelDef {
Error, Warn, Info, Debug, Trace, Error,
Warn,
Info,
Debug,
Trace,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Settings { pub struct Settings {
#[serde(with = "LogLevelDef")] #[serde(with = "LogLevelDef")]
log_level: log::Level, log_level: log::Level,
completions: HashMap<String, String>, completions: HashMap<String, String>,
sources: Sources, sources: Sources,
theme: Theme,
} }
impl Settings { impl Settings {
@ -74,10 +101,12 @@ impl Settings {
config.merge(config::File::with_name("default"))?; config.merge(config::File::with_name("default"))?;
config.merge(config::Environment::new() config.merge(
.prefix("roftl") config::Environment::new()
.separator("_") .prefix("roftl")
.ignore_empty(true))?; .separator("_")
.ignore_empty(true),
)?;
config.try_into() config.try_into()
} }
@ -95,7 +124,9 @@ impl Settings {
} }
pub fn sources(&self) -> Vec<Box<dyn super::shared::Source>> { pub fn sources(&self) -> Vec<Box<dyn super::shared::Source>> {
self.sources.additional.source self.sources
.additional
.source
.iter() .iter()
.map(|s| s.init()) .map(|s| s.init())
.collect() .collect()

View file

@ -9,11 +9,13 @@ pub struct Entry {
pub trait Source: Send + Sync { pub trait Source: Send + Sync {
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
fn entries(&mut self) -> Vec<Entry>; fn entries(&mut self) -> Vec<Entry>;
fn actions(&self, _entry: &Entry) -> Vec<String> fn actions(&self, _entry: &Entry) -> Vec<String> {
{ vec![] } vec![]
}
fn exec_default(&self, entry: &Entry) fn exec_default(&self, entry: &Entry) {
{ self.exec_action(entry, 0) } self.exec_action(entry, 0)
}
fn exec_action(&self, entry: &Entry, action: u8); fn exec_action(&self, entry: &Entry, action: u8);
} }

View file

@ -1,25 +1,27 @@
use log::{debug, trace}; use log::{debug, trace};
use std::error::Error; use std::error::Error;
use winit::window::{Window, WindowBuilder}; use winit::event::{
ElementState, Event, KeyboardInput, VirtualKeyCode,
WindowEvent::{CloseRequested, ReceivedCharacter},
};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, use winit::window::{Window, WindowBuilder};
WindowEvent::{CloseRequested, ReceivedCharacter}};
use self::core::roftl::Roftl; use self::core::roftl::Roftl;
mod ui;
mod sources;
mod matcher;
mod core; mod core;
mod matcher;
mod sources;
mod ui;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
let settings = core::settings::Settings::parse()?; let settings = core::settings::Settings::parse()?;
debug!{"Settings {:?}", settings} debug! {"Settings {:?}", settings}
debug!{"Set up roftl"}; debug! {"Set up roftl"};
let mut roftl = Roftl::new() let mut roftl = Roftl::new()
.with_primary_source(settings.primary_source()) .with_primary_source(settings.primary_source())
.with_primary_matcher(settings.primary_matcher()) .with_primary_matcher(settings.primary_matcher())
@ -30,10 +32,10 @@ fn main() -> Result<(), Box<dyn Error>> {
roftl = roftl.add_source(source); roftl = roftl.add_source(source);
} }
debug!{"Source roftl sources"} debug! {"Source roftl sources"}
roftl.source(); roftl.source();
debug!{"Build window"} debug! {"Build window"}
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_decorations(false) .with_decorations(false)
@ -41,12 +43,12 @@ fn main() -> Result<(), Box<dyn Error>> {
.build(&event_loop) .build(&event_loop)
.expect("Could not create window"); .expect("Could not create window");
debug!{"Window id: {:?}", window.id()} debug! {"Window id: {:?}", window.id()}
debug!{"Draw primary state to window"} debug! {"Draw primary state to window"}
ui::draw(&window, "", roftl.narrow(""), 0); ui::draw(&window, "", roftl.narrow(""), 0);
debug!{"Start event loop"} debug! {"Start event loop"}
let roftl_loop = RoftlLoop::new(roftl, window); let roftl_loop = RoftlLoop::new(roftl, window);
roftl_loop.run(event_loop); roftl_loop.run(event_loop);
} }
@ -66,51 +68,55 @@ struct RoftlLoop {
} }
impl RoftlLoop { impl RoftlLoop {
fn new(roftl: Roftl, window: Window) -> Self fn new(roftl: Roftl, window: Window) -> Self {
{ RoftlLoop {
RoftlLoop{input_buffer: String::default(), input_changed: true, selection: 0, input_buffer: String::default(),
mode: Mode::Selection, input_changed: true,
roftl, window} selection: 0,
mode: Mode::Selection,
roftl,
window,
}
} }
fn run<T>(mut self, event_loop: EventLoop<T>) -> ! fn run<T>(mut self, event_loop: EventLoop<T>) -> ! {
{
event_loop.run(move |evt, _win, flow| { event_loop.run(move |evt, _win, flow| {
*flow = ControlFlow::Wait; *flow = ControlFlow::Wait;
match evt { match evt {
Event::WindowEvent{event: CloseRequested, window_id} Event::WindowEvent {
if window_id == self.window.id() => event: CloseRequested,
{ window_id,
} if window_id == self.window.id() => {
*flow = ControlFlow::Exit; *flow = ControlFlow::Exit;
} }
Event::WindowEvent{event: ReceivedCharacter(character), window_id} Event::WindowEvent {
if window_id == self.window.id() event: ReceivedCharacter(character),
&& matches!(self.mode, Mode::Selection) => window_id,
{ } if window_id == self.window.id() && matches!(self.mode, Mode::Selection) => {
*flow = self.process_character(character); *flow = self.process_character(character);
} }
Event::WindowEvent{event: ReceivedCharacter(character), window_id} Event::WindowEvent {
if window_id == self.window.id() event: ReceivedCharacter(character),
&& matches!(self.mode, Mode::Actions) => window_id,
{ } if window_id == self.window.id() && matches!(self.mode, Mode::Actions) => {
*flow = self.process_action(character); *flow = self.process_action(character);
} }
Event::WindowEvent{event: winit::event::WindowEvent::KeyboardInput{input, ..}, window_id} Event::WindowEvent {
if window_id == self.window.id() => event: winit::event::WindowEvent::KeyboardInput { input, .. },
{ window_id,
} if window_id == self.window.id() => {
self.process_input(input); self.process_input(input);
} }
Event::RedrawRequested(window_id) Event::RedrawRequested(window_id)
if window_id == self.window.id() if window_id == self.window.id() && matches! {self.mode, Mode::Selection} =>
&& matches!{self.mode, Mode::Selection} =>
{ {
let results = self.roftl.narrow(&self.input_buffer); let results = self.roftl.narrow(&self.input_buffer);
trace!{"Narrow result {:?}", results} trace! {"Narrow result {:?}", results}
// quick switch if only one result // quick switch if only one result
// but make sure that input buffer is filled, otherwise will immediately close // but make sure that input buffer is filled, otherwise will immediately close
@ -122,14 +128,17 @@ impl RoftlLoop {
} }
// correct selection for results // correct selection for results
if results.len() > 0 { self.selection = self.selection % results.len() } if results.len() > 0 {
else { self.selection = 0 } self.selection = self.selection % results.len()
} else {
self.selection = 0
}
if self.input_changed { if self.input_changed {
trace!{"Redrawing with input {}", self.input_buffer} trace! {"Redrawing with input {}", self.input_buffer}
ui::draw(&self.window, &self.input_buffer, results, self.selection); ui::draw(&self.window, &self.input_buffer, results, self.selection);
} else { } else {
trace!{"Quick redraw with input {}", self.input_buffer} trace! {"Quick redraw with input {}", self.input_buffer}
ui::redraw_quick(&self.window, results, self.selection); ui::redraw_quick(&self.window, results, self.selection);
} }
@ -137,34 +146,37 @@ impl RoftlLoop {
} }
Event::RedrawRequested(window_id) Event::RedrawRequested(window_id)
if window_id == self.window.id() if window_id == self.window.id() && matches! {self.mode, Mode::Actions} =>
&& matches!{self.mode, Mode::Actions} =>
{ {
let actions = self.roftl.actions(self.selection); let actions = self.roftl.actions(self.selection);
debug!{"Actions for current selection: {:?}", actions} debug! {"Actions for current selection: {:?}", actions}
ui::draw_actions(&self.window, actions, &self.input_buffer); ui::draw_actions(&self.window, actions, &self.input_buffer);
} }
_ => () _ => (),
} }
}); });
} }
fn process_input(&mut self, input: KeyboardInput) -> ControlFlow { fn process_input(&mut self, input: KeyboardInput) -> ControlFlow {
if let KeyboardInput { virtual_keycode: Some(code), state, .. } = input { if let KeyboardInput {
virtual_keycode: Some(code),
state,
..
} = input
{
return match (code, state) { return match (code, state) {
(VirtualKeyCode::Down, ElementState::Released) => { (VirtualKeyCode::Down, ElementState::Released) => {
debug!("Received down"); debug!("Received down");
ControlFlow::Wait ControlFlow::Wait
}, }
(VirtualKeyCode::Up, ElementState::Released) => { (VirtualKeyCode::Up, ElementState::Released) => {
debug!("Received up"); debug!("Received up");
ControlFlow::Wait ControlFlow::Wait
}, }
_ => ControlFlow::Wait, _ => ControlFlow::Wait,
} };
} }
ControlFlow::Wait ControlFlow::Wait
@ -172,14 +184,14 @@ impl RoftlLoop {
fn process_character(&mut self, character: char) -> ControlFlow { fn process_character(&mut self, character: char) -> ControlFlow {
match character { match character {
'q' | 'Q' => ControlFlow::Exit, 'q' | 'Q' => ControlFlow::Exit,
// Escape // Escape
c if c == char::from(0x1b) => ControlFlow::Exit, c if c == char::from(0x1b) => ControlFlow::Exit,
// Backspace // Backspace
c if c == char::from(0x08) => { c if c == char::from(0x08) => {
trace!{"Retrieved backspace. Pop char from input"} trace! {"Retrieved backspace. Pop char from input"}
self.input_buffer.pop(); self.input_buffer.pop();
self.input_changed = true; self.input_changed = true;
self.window.request_redraw(); self.window.request_redraw();
@ -188,14 +200,14 @@ impl RoftlLoop {
// Enter // Enter
c if c == char::from(0x0d) => { c if c == char::from(0x0d) => {
trace!{"Retrieved enter. Trigger primary action"} trace! {"Retrieved enter. Trigger primary action"}
self.roftl.exec_action(self.selection, 0); self.roftl.exec_action(self.selection, 0);
ControlFlow::Exit ControlFlow::Exit
} }
// Ctrl+n // Ctrl+n
c if c == char::from(0x0e) => { c if c == char::from(0x0e) => {
debug!{"Received next"} debug! {"Received next"}
self.selection += 1; self.selection += 1;
self.window.request_redraw(); self.window.request_redraw();
ControlFlow::Wait ControlFlow::Wait
@ -203,30 +215,38 @@ impl RoftlLoop {
// Ctrl+p // Ctrl+p
c if c == char::from(0x10) => { c if c == char::from(0x10) => {
debug!{"Received previous"} debug! {"Received previous"}
if self.selection != 0 { self.selection -= 1 } if self.selection != 0 {
self.selection -= 1
}
self.window.request_redraw(); self.window.request_redraw();
ControlFlow::Wait ControlFlow::Wait
} }
// tab // tab
c if c == char::from(0x09) => { c if c == char::from(0x09) => {
debug!{"Received actions"} debug! {"Received actions"}
match self.mode { match self.mode {
Mode::Selection => { self.mode = Mode::Actions; self.input_changed = true }, Mode::Selection => {
Mode::Actions => { self.mode = Mode::Selection; self.input_changed = true } self.mode = Mode::Actions;
self.input_changed = true
}
Mode::Actions => {
self.mode = Mode::Selection;
self.input_changed = true
}
} }
self.window.request_redraw(); self.window.request_redraw();
ControlFlow::Wait ControlFlow::Wait
} }
c if c.is_control() => { c if c.is_control() => {
debug!{"Got unknown control character {:#x}", u32::from(c)} debug! {"Got unknown control character {:#x}", u32::from(c)}
ControlFlow::Wait ControlFlow::Wait
} }
_ => { _ => {
trace!{"Push {} to input buffer", character} trace! {"Push {} to input buffer", character}
self.input_buffer.push(character); self.input_buffer.push(character);
self.input_changed = true; self.input_changed = true;
self.window.request_redraw(); self.window.request_redraw();
@ -238,47 +258,52 @@ impl RoftlLoop {
fn process_action(&mut self, character: char) -> ControlFlow { fn process_action(&mut self, character: char) -> ControlFlow {
match character { match character {
'1' => { '1' => {
trace!{"Retrieved action selection 1. Trigger primary action"} trace! {"Retrieved action selection 1. Trigger primary action"}
self.roftl.exec_action(self.selection, 0); self.roftl.exec_action(self.selection, 0);
ControlFlow::Exit ControlFlow::Exit
}, }
'2' => { '2' => {
trace!{"Retrieved action selection 2. Trigger secondary action"} trace! {"Retrieved action selection 2. Trigger secondary action"}
self.roftl.exec_action(self.selection, 1); self.roftl.exec_action(self.selection, 1);
ControlFlow::Exit ControlFlow::Exit
}, }
'3' => { '3' => {
trace!{"Retrieved action selection 3. Trigger tertiary action"} trace! {"Retrieved action selection 3. Trigger tertiary action"}
self.roftl.exec_action(self.selection, 2); self.roftl.exec_action(self.selection, 2);
ControlFlow::Exit ControlFlow::Exit
}, }
'4' => { '4' => {
trace!{"Retrieved action selection 4. Trigger quaternary action"} trace! {"Retrieved action selection 4. Trigger quaternary action"}
self.roftl.exec_action(self.selection, 3); self.roftl.exec_action(self.selection, 3);
ControlFlow::Exit ControlFlow::Exit
}, }
'q' | 'Q' => ControlFlow::Exit, 'q' | 'Q' => ControlFlow::Exit,
// Escape // Escape
c if c == char::from(0x1b) => ControlFlow::Exit, c if c == char::from(0x1b) => ControlFlow::Exit,
// tab // tab
c if c == char::from(0x09) => { c if c == char::from(0x09) => {
debug!{"Received actions"} debug! {"Received actions"}
match self.mode { match self.mode {
Mode::Selection => { self.mode = Mode::Actions; self.input_changed = true }, Mode::Selection => {
Mode::Actions => { self.mode = Mode::Selection; self.input_changed = true } self.mode = Mode::Actions;
self.input_changed = true
}
Mode::Actions => {
self.mode = Mode::Selection;
self.input_changed = true
}
} }
self.window.request_redraw(); self.window.request_redraw();
ControlFlow::Wait ControlFlow::Wait
}, }
_ => { _ => {
debug!{"Retrieved unknown action selection. Processing character {}", character} debug! {"Retrieved unknown action selection. Processing character {}", character}
ControlFlow::Wait ControlFlow::Wait
}, }
} }
} }
} }

View file

@ -7,19 +7,17 @@ pub struct FlxMatcher;
impl FlxMatcher { impl FlxMatcher {
pub fn new() -> Box<Self> { pub fn new() -> Box<Self> {
Box::new(FlxMatcher{}) Box::new(FlxMatcher {})
} }
} }
impl Matcher for FlxMatcher { impl Matcher for FlxMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> { fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
debug!{"Searching {} in {}", needle, haystack} debug! {"Searching {} in {}", needle, haystack}
let res: Option<Score> = flx_rs::score(haystack, needle); let res: Option<Score> = flx_rs::score(haystack, needle);
res.and_then(|score| { res.and_then(|score| {
let s = score.score as f64; let s = score.score as f64;
let i = score.indices.into_iter() let i = score.indices.into_iter().map(|idx| idx as usize).collect();
.map(|idx| idx as usize)
.collect();
Some((s, i)) Some((s, i))
}) })

View file

@ -5,18 +5,20 @@ use fuse_rust::Fuse;
use super::core::shared::Matcher; use super::core::shared::Matcher;
fn flatten_ranges(ranges: Vec<Range<usize>>) -> Vec<usize> { fn flatten_ranges(ranges: Vec<Range<usize>>) -> Vec<usize> {
ranges.into_iter().flat_map(|range| {range.collect::<Vec<usize>>().into_iter()}) ranges
.into_iter()
.flat_map(|range| range.collect::<Vec<usize>>().into_iter())
.collect() .collect()
} }
pub struct FuseMatcher { pub struct FuseMatcher {
matcher: Fuse matcher: Fuse,
} }
impl FuseMatcher { impl FuseMatcher {
pub fn new() -> Box<Self> { pub fn new() -> Box<Self> {
Box::new(FuseMatcher{ Box::new(FuseMatcher {
matcher: Fuse::default() matcher: Fuse::default(),
}) })
} }
} }
@ -25,9 +27,7 @@ impl Matcher for FuseMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> { fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
match self.matcher.search_text_in_string(needle, haystack) { match self.matcher.search_text_in_string(needle, haystack) {
Some(score) => Some((score.score, flatten_ranges(score.ranges))), Some(score) => Some((score.score, flatten_ranges(score.ranges))),
None => None None => None,
} }
} }
} }

View file

@ -11,4 +11,3 @@ pub use fuse::FuseMatcher;
mod flx; mod flx;
pub use flx::FlxMatcher; pub use flx::FlxMatcher;

View file

@ -4,7 +4,7 @@ pub struct PrefixMatcher;
impl PrefixMatcher { impl PrefixMatcher {
pub fn new() -> Box<Self> { pub fn new() -> Box<Self> {
Box::new(PrefixMatcher{}) Box::new(PrefixMatcher {})
} }
} }

View file

@ -1,15 +1,15 @@
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2}; use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use super::core::shared::Matcher; use super::core::shared::Matcher;
pub struct SkimMatcher { pub struct SkimMatcher {
matcher: SkimMatcherV2 matcher: SkimMatcherV2,
} }
impl SkimMatcher { impl SkimMatcher {
pub fn new() -> Box<Self> { pub fn new() -> Box<Self> {
Box::new(SkimMatcher{ Box::new(SkimMatcher {
matcher: SkimMatcherV2::default() matcher: SkimMatcherV2::default(),
}) })
} }
} }
@ -17,8 +17,8 @@ impl SkimMatcher {
impl Matcher for SkimMatcher { impl Matcher for SkimMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> { fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
match self.matcher.fuzzy_indices(haystack, needle) { match self.matcher.fuzzy_indices(haystack, needle) {
Some((score, indices)) => Some(((i64::MAX as f64/score as f64), indices)), Some((score, indices)) => Some(((i64::MAX as f64 / score as f64), indices)),
None => None None => None,
} }
} }
} }

View file

@ -1,9 +1,12 @@
use std::{os::unix::prelude::CommandExt, process::{Command, Stdio}}; use std::{
os::unix::prelude::CommandExt,
process::{Command, Stdio},
};
use freedesktop_entry_parser as parser; use freedesktop_entry_parser as parser;
use log::{debug, error, trace}; use log::{debug, error, trace};
use walkdir::WalkDir;
use nix::unistd::{getpid, setpgid}; use nix::unistd::{getpid, setpgid};
use walkdir::WalkDir;
use super::core::shared::{Entry, Source}; use super::core::shared::{Entry, Source};
@ -11,7 +14,7 @@ use super::core::shared::{Entry, Source};
struct App { struct App {
name: String, name: String,
command: String, command: String,
terminal: bool terminal: bool,
} }
pub fn parse_command_string<'a>(exec: &'a str) -> (String, Vec<String>) { pub fn parse_command_string<'a>(exec: &'a str) -> (String, Vec<String>) {
@ -27,13 +30,13 @@ pub fn run_bg(mut command: Command) {
command.pre_exec(|| { command.pre_exec(|| {
let pid = getpid(); let pid = getpid();
if let Err(_) = setpgid(pid, pid) { if let Err(_) = setpgid(pid, pid) {
error!{"Failed to set pgid of child process with pid {}", pid}; error! {"Failed to set pgid of child process with pid {}", pid};
} }
Ok(()) Ok(())
}); });
} }
debug!{"Running command {:?}", command} debug! {"Running command {:?}", command}
command.spawn().unwrap(); command.spawn().unwrap();
} }
@ -50,27 +53,23 @@ impl App {
command.args(&args); command.args(&args);
let _ = run_bg(command); let _ = run_bg(command);
} else { } else {
debug!("Running `{} {}`", cmd, args.join(" "));
debug!("Running `{} {}`", cmd, args.join(" ")); let mut command = Command::new(&cmd);
let mut command = Command::new(&cmd); command.args(&args);
command.args(&args); let _ = run_bg(command);
let _ = run_bg(command);
} }
} }
} }
impl From<&App> for Entry impl From<&App> for Entry {
{
fn from(app: &App) -> Self { fn from(app: &App) -> Self {
Entry { Entry {
name: app.name.clone(), name: app.name.clone(),
description: "".into(), description: "".into(),
source: "apps", source: "apps",
identifier: 0 identifier: 0,
} }
} }
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -78,45 +77,52 @@ struct AppBuilder<'a> {
name: Option<&'a str>, name: Option<&'a str>,
exec: Option<&'a str>, exec: Option<&'a str>,
hidden: Option<&'a str>, hidden: Option<&'a str>,
terminal: Option<&'a str> terminal: Option<&'a str>,
} }
impl<'a> AppBuilder<'a> impl<'a> AppBuilder<'a> {
{ fn build(&self) -> Option<App> {
fn build(&self) -> Option<App> if self.name.is_none() {
{ return None;
if self.name.is_none() { return None } }
if self.exec.is_none() { return None } if self.exec.is_none() {
if self.hidden.is_some() { return None } return None;
}
if self.hidden.is_some() {
return None;
}
Some ( Some(App {
App { name: self.name.unwrap().into(),
name: self.name.unwrap().into(), command: strip_entry_args(self.exec.unwrap()).into(),
command: strip_entry_args(self.exec.unwrap()).into(), terminal: self
terminal: self.terminal.map_or(false, |term| term.parse().unwrap_or(false)) .terminal
}) .map_or(false, |term| term.parse().unwrap_or(false)),
})
} }
} }
pub struct Apps { pub struct Apps {
entries: Vec<App> entries: Vec<App>,
} }
impl Apps { impl Apps {
pub fn new() -> Box<Apps> pub fn new() -> Box<Apps> {
{ Box::new(Apps { entries: vec![] })
Box::new(Apps{entries: vec![]})
} }
} }
fn strip_entry_args(exec: &str) -> String { fn strip_entry_args(exec: &str) -> String {
trace!{"Stripping {}", exec} trace! {"Stripping {}", exec}
let iter = exec.split(' '); let iter = exec.split(' ');
let result = iter.filter(|item| !item.starts_with('%')) let result = iter
.filter(|item| !item.starts_with('%'))
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join(" "); .join(" ")
.trim_matches('"')
.into();
trace!{"Stripped into {}", result} trace! {"Stripped into {}", result}
result result
} }
@ -130,7 +136,7 @@ impl Source for Apps {
"/usr/share/applications/", "/usr/share/applications/",
"/home/armin/.local/share/applications/", "/home/armin/.local/share/applications/",
"/var/lib/snapd/desktop/applications", "/var/lib/snapd/desktop/applications",
"/var/lib/flatpak/exports/share/applications" "/var/lib/flatpak/exports/share/applications",
]; ];
let desktop_entries: Vec<parser::Entry> = paths let desktop_entries: Vec<parser::Entry> = paths
@ -147,41 +153,37 @@ impl Source for Apps {
let mut app_builder = AppBuilder::default(); let mut app_builder = AppBuilder::default();
let section = entry.section("Desktop Entry"); let section = entry.section("Desktop Entry");
for attr in section.attrs() for attr in section.attrs() {
{ match attr.name {
match attr.name
{
"Name" => app_builder.name = attr.value, "Name" => app_builder.name = attr.value,
"Exec" => app_builder.exec = attr.value, "Exec" => app_builder.exec = attr.value,
"NoDisplay" => app_builder.hidden = attr.value, "NoDisplay" => app_builder.hidden = attr.value,
"Hidden" => app_builder.hidden = attr.value, "Hidden" => app_builder.hidden = attr.value,
"Terminal" => app_builder.terminal = attr.value, "Terminal" => app_builder.terminal = attr.value,
_ => () _ => (),
} }
} }
if let Some(app) = app_builder.build() if let Some(app) = app_builder.build() {
{
let mut entry: Entry = (&app).into(); let mut entry: Entry = (&app).into();
entry.identifier = idx; entry.identifier = idx;
idx += 1; idx += 1;
entries.push(entry); entries.push(entry);
self.entries.push(app); self.entries.push(app);
} }
} }
trace!{"Got desktop entries: {:?}", entries} trace! {"Got desktop entries: {:?}", entries}
entries entries
} }
fn exec_action(&self, entry: &Entry, action: u8) { fn exec_action(&self, entry: &Entry, action: u8) {
debug!{"Got desktop entry {:?} for action", entry} debug! {"Got desktop entry {:?} for action", entry}
debug!{"Desktop entry has id {}", entry.identifier} debug! {"Desktop entry has id {}", entry.identifier}
trace!{"Apps has entries {:?}", self.entries} trace! {"Apps has entries {:?}", self.entries}
let app = self.entries.get(entry.identifier as usize).unwrap(); let app = self.entries.get(entry.identifier as usize).unwrap();
debug!{"Doing primary action {} for {}", app.name, app.command} debug! {"Doing primary action {} for {}", app.name, app.command}
app.run(); app.run();
} }
} }

View file

@ -1,6 +1,10 @@
use std::{os::unix::prelude::CommandExt, path::PathBuf, process::{Command, Stdio}};
use log::{debug, error}; use log::{debug, error};
use nix::unistd::{getpid, setpgid}; use nix::unistd::{getpid, setpgid};
use std::{
os::unix::prelude::CommandExt,
path::PathBuf,
process::{Command, Stdio},
};
use walkdir::WalkDir; use walkdir::WalkDir;
use super::core::shared::{Entry, Source}; use super::core::shared::{Entry, Source};
@ -11,7 +15,7 @@ pub struct ShellHost {
impl ShellHost { impl ShellHost {
pub fn new() -> Box<ShellHost> { pub fn new() -> Box<ShellHost> {
Box::new(ShellHost{ scripts: vec![] }) Box::new(ShellHost { scripts: vec![] })
} }
} }
@ -25,35 +29,34 @@ impl Source for ShellHost {
let script_path = "/home/armin/dev/incubator/roftl/sh"; let script_path = "/home/armin/dev/incubator/roftl/sh";
// TODO make robust if path does not exist // TODO make robust if path does not exist
let scripts: Vec<PathBuf> = WalkDir::new(script_path).into_iter() let scripts: Vec<PathBuf> = WalkDir::new(script_path)
.into_iter()
.map(|e| e.unwrap().path().to_path_buf()) .map(|e| e.unwrap().path().to_path_buf())
.collect(); .collect();
let mut entries: Vec<Entry> = vec![]; let mut entries: Vec<Entry> = vec![];
for (idx, script) in scripts.iter().enumerate() { for (idx, script) in scripts.iter().enumerate() {
entries.push( entries.push(Entry {
Entry { source: self.name(),
source: self.name(), name: script.file_stem().unwrap().to_str().unwrap().into(),
name: script.file_stem().unwrap().to_str().unwrap().into(), description: "".into(),
description: "".into(), identifier: idx as u64,
identifier: idx as u64 });
});
} }
self.scripts = scripts; self.scripts = scripts;
entries entries
} }
fn exec_action(&self, entry: &Entry, action: u8) { fn exec_action(&self, entry: &Entry, action: u8) {
let script: &PathBuf = self.scripts.get(entry.identifier as usize).unwrap(); let script: &PathBuf = self.scripts.get(entry.identifier as usize).unwrap();
debug!{"Got script {:?}", script}; debug! {"Got script {:?}", script};
let mut command = Command::new("xfce4-terminal"); let mut command = Command::new("xfce4-terminal");
let args = vec!["-x", script.to_str().unwrap()]; let args = vec!["-x", script.to_str().unwrap()];
command.args(&args); command.args(&args);
let _ = run_bg(command); let _ = run_bg(command);
debug!{"Executed {:?}", script}; debug! {"Executed {:?}", script};
} }
} }
@ -63,12 +66,12 @@ pub fn run_bg(mut command: Command) {
command.pre_exec(|| { command.pre_exec(|| {
let pid = getpid(); let pid = getpid();
if let Err(_) = setpgid(pid, pid) { if let Err(_) = setpgid(pid, pid) {
error!{"Failed to set pgid of child process with pid {}", pid}; error! {"Failed to set pgid of child process with pid {}", pid};
} }
Ok(()) Ok(())
}); });
} }
debug!{"Running command {:?}", command} debug! {"Running command {:?}", command}
command.spawn().unwrap(); command.spawn().unwrap();
} }

View file

@ -44,7 +44,8 @@ impl Source for TestSource {
fn actions(&self, _entry: &Entry) -> Vec<String> { fn actions(&self, _entry: &Entry) -> Vec<String> {
["Primary", "Secondary", "Tertiary", "Quaternary"] ["Primary", "Secondary", "Tertiary", "Quaternary"]
.iter().map(|s| (*s).into()).collect() .iter()
.map(|s| (*s).into())
.collect()
} }
} }

View file

@ -8,15 +8,21 @@ use xcb_util::ffi::ewmh::XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER;
use xcb_util::icccm; use xcb_util::icccm;
pub struct Window { pub struct Window {
action_data: HashMap<u64, (u32, i32)> action_data: HashMap<u64, (u32, i32)>,
} }
impl Window { impl Window {
pub fn new() -> Box<Window> { pub fn new() -> Box<Window> {
Box::new(Window { action_data: HashMap::<u64, (u32,i32)>::new() }) Box::new(Window {
action_data: HashMap::<u64, (u32, i32)>::new(),
})
} }
fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result<bool, xcb::ReplyError> { fn is_normal_window(
&self,
con: &ewmh::Connection,
window: u32,
) -> Result<bool, xcb::ReplyError> {
let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?; let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?;
for atom in wm_type_reply.atoms() { for atom in wm_type_reply.atoms() {
@ -28,48 +34,95 @@ impl Window {
Ok(false) Ok(false)
} }
fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> { fn window_category(
Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into()) &self,
con: &ewmh::Connection,
window: u32,
) -> Result<String, xcb::ReplyError> {
Ok(icccm::get_wm_class(&con, window)
.get_reply()?
.class()
.into())
} }
fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> { fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into()) Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into())
} }
fn switch_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { fn switch_window(
&self,
con: &ewmh::Connection,
window: u32,
screen: i32,
) -> Result<(), xcb::ReplyError> {
let window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?; let window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?;
let active_window = ewmh::get_active_window(con, screen).get_reply()?; let active_window = ewmh::get_active_window(con, screen).get_reply()?;
ewmh::set_current_desktop(con, screen, window_desktop); ewmh::set_current_desktop(con, screen, window_desktop);
ewmh::request_change_active_window(con, screen, window, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, XCB_CURRENT_TIME, active_window).request_check().unwrap(); ewmh::request_change_active_window(
ewmh::set_active_window(con, screen, window).request_check().unwrap(); con,
screen,
window,
XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER,
XCB_CURRENT_TIME,
active_window,
)
.request_check()
.unwrap();
ewmh::set_active_window(con, screen, window)
.request_check()
.unwrap();
xcb::set_input_focus(&con, 0, window, XCB_CURRENT_TIME); xcb::set_input_focus(&con, 0, window, XCB_CURRENT_TIME);
Ok(()) Ok(())
} }
fn toggle_maximize_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { fn toggle_maximize_window(
&self,
con: &ewmh::Connection,
window: u32,
screen: i32,
) -> Result<(), xcb::ReplyError> {
let max_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con); let max_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con);
let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con); let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con);
let action_atom = ewmh::STATE_TOGGLE; let action_atom = ewmh::STATE_TOGGLE;
debug!{"Toggle maximize for {}", window} debug! {"Toggle maximize for {}", window}
ewmh::request_change_wm_state(con, screen, window, action_atom, max_horz_atom, max_vert_atom, 0).request_check()?; ewmh::request_change_wm_state(
con,
screen,
window,
action_atom,
max_horz_atom,
max_vert_atom,
0,
)
.request_check()?;
Ok(()) Ok(())
} }
fn close_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { fn close_window(
debug!{"Toggle maximize for {}", window} &self,
con: &ewmh::Connection,
window: u32,
screen: i32,
) -> Result<(), xcb::ReplyError> {
debug! {"Toggle maximize for {}", window}
ewmh::request_close_window(con, screen, window, XCB_CURRENT_TIME, 0).request_check() ewmh::request_close_window(con, screen, window, XCB_CURRENT_TIME, 0).request_check()
} }
fn toggle_hide_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { fn toggle_hide_window(
&self,
con: &ewmh::Connection,
window: u32,
screen: i32,
) -> Result<(), xcb::ReplyError> {
let hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con); let hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con);
let action_atom = ewmh::STATE_TOGGLE; let action_atom = ewmh::STATE_TOGGLE;
debug!{"Toggle hidden for {}", window} debug! {"Toggle hidden for {}", window}
ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0).request_check()?; ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0)
.request_check()?;
Ok(()) Ok(())
} }
@ -81,10 +134,15 @@ impl Source for Window {
} }
fn entries(&mut self) -> Vec<Entry> { fn entries(&mut self) -> Vec<Entry> {
let (xcb_conn, screen_num) = xcb::base::Connection::connect(None).expect("Could not connect to X server"); let (xcb_conn, screen_num) =
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap(); xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
let client_list_reply = xcb_util::ewmh::get_client_list(&ewmh_conn, screen_num).get_reply().unwrap(); let client_list_reply = xcb_util::ewmh::get_client_list(&ewmh_conn, screen_num)
.get_reply()
.unwrap();
let windows = client_list_reply.windows(); let windows = client_list_reply.windows();
let mut entries: Vec<Entry> = vec![]; let mut entries: Vec<Entry> = vec![];
@ -92,7 +150,7 @@ impl Source for Window {
let mut count: u64 = 0; let mut count: u64 = 0;
for w in windows { for w in windows {
match self.is_normal_window(&ewmh_conn, *w) { match self.is_normal_window(&ewmh_conn, *w) {
Ok(true) => {}, Ok(true) => {}
Ok(false) => continue, Ok(false) => continue,
Err(_err) => {} Err(_err) => {}
} }
@ -102,10 +160,15 @@ impl Source for Window {
debug!("Found window {} - {}", title, category); debug!("Found window {} - {}", title, category);
entries.push(Entry{source: self.name(), name: category, description: title, identifier: count}); entries.push(Entry {
source: self.name(),
name: category,
description: title,
identifier: count,
});
self.action_data.insert(count, (*w, screen_num)); self.action_data.insert(count, (*w, screen_num));
count+=1; count += 1;
} }
entries entries
@ -117,36 +180,54 @@ impl Source for Window {
match action { match action {
0 => { 0 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap(); let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) = xcb::base::Connection::connect(None).expect("Could not connect to X server"); let (xcb_conn, _screen_num) =
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap(); xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.switch_window(&ewmh_conn, window, screen).unwrap() self.switch_window(&ewmh_conn, window, screen).unwrap()
}, }
1 => { 1 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap(); let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) = xcb::base::Connection::connect(None).expect("Could not connect to X server"); let (xcb_conn, _screen_num) =
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap(); xcb::base::Connection::connect(None).expect("Could not connect to X server");
self.toggle_maximize_window(&ewmh_conn, window, screen).unwrap(); let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.toggle_maximize_window(&ewmh_conn, window, screen)
.unwrap();
self.switch_window(&ewmh_conn, window, screen).unwrap() self.switch_window(&ewmh_conn, window, screen).unwrap()
}, }
2 => { 2 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap(); let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) = xcb::base::Connection::connect(None).expect("Could not connect to X server"); let (xcb_conn, _screen_num) =
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap(); xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.toggle_hide_window(&ewmh_conn, window, screen).unwrap(); self.toggle_hide_window(&ewmh_conn, window, screen).unwrap();
}, }
3 => { 3 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap(); let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) = xcb::base::Connection::connect(None).expect("Could not connect to X server"); let (xcb_conn, _screen_num) =
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap(); xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.close_window(&ewmh_conn, window, screen).unwrap(); self.close_window(&ewmh_conn, window, screen).unwrap();
}, }
_ => panic!{"Unknown action {:?}", action} _ => panic! {"Unknown action {:?}", action},
} }
} }
fn actions(&self, _entry: &Entry) -> Vec<String> { fn actions(&self, _entry: &Entry) -> Vec<String> {
vec!["Switch".into(), "Toggle maximize".into(), "Toggle hide".into(), "Close".into()] vec![
"Switch".into(),
"Toggle maximize".into(),
"Toggle hide".into(),
"Close".into(),
]
} }
} }

View file

@ -2,8 +2,8 @@ use super::core;
mod ui; mod ui;
pub use ui::draw; pub use ui::draw;
pub use ui::redraw_quick;
pub use ui::draw_actions; pub use ui::draw_actions;
pub use ui::redraw_quick;
mod painter; mod painter;
mod theme; mod theme;

View file

@ -9,11 +9,13 @@ pub struct Painter<'a> {
impl<'a> Painter<'a> { impl<'a> Painter<'a> {
pub fn new(context: &'a cairo::Context) -> Self { pub fn new(context: &'a cairo::Context) -> Self {
Painter{context, theme: Theme::default()} Painter {
context,
theme: Theme::default(),
}
} }
pub fn background(&self) pub fn background(&self) {
{
let ctx = self.context; let ctx = self.context;
// reset clip so that we can draw the entire background of the surface // reset clip so that we can draw the entire background of the surface
@ -25,42 +27,47 @@ impl<'a> Painter<'a> {
ctx.paint().expect("Could not paint background"); ctx.paint().expect("Could not paint background");
// draw the border // draw the border
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap(); let (x1, y1, x2, y2) = ctx.clip_extents().unwrap();
debug!{"Border width: {}", self.theme.border(ctx)}; debug! {"Border width: {}", self.theme.border(ctx)};
let border = self.theme.border(ctx)/2.0; let border = self.theme.border(ctx) / 2.0;
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border); let (x1, y1, x2, y2) = (x1 + border, y1 + border, x2 - x1 - border, y2 - y1 - border);
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2}; debug! {"Border rect: {},{},{},{}", x1, y1, x2, y2};
ctx.rectangle(x1,y1,x2-x1,y2-y1); ctx.rectangle(x1, y1, x2 - x1, y2 - y1);
ctx.theme_border(&self.theme); ctx.theme_border(&self.theme);
ctx.stroke().unwrap(); ctx.stroke().unwrap();
self.border_clip(); self.border_clip();
} }
pub fn border_clip(&self) pub fn border_clip(&self) {
{
let ctx = self.context; let ctx = self.context;
ctx.reset_clip(); ctx.reset_clip();
// clip any future content so the border stays alive // clip any future content so the border stays alive
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap(); // the path of the clip let (x1, y1, x2, y2) = ctx.clip_extents().unwrap(); // the path of the clip
// region, i.e. the surface // region, i.e. the surface
debug!{"Border extent: {},{},{},{}", x1, y1, x2, y2}; debug! {"Border extent: {},{},{},{}", x1, y1, x2, y2};
let border = self.theme.border(ctx); // width of the border, note let border = self.theme.border(ctx); // width of the border, note
// that half of the border is outside // that half of the border is outside
// the clip_extents // the clip_extents
// let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border); // let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border);
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2}; debug! {"Border rect: {},{},{},{}", x1, y1, x2, y2};
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border-border); let (x1, y1, x2, y2) = (
debug!{"Border clip: {},{},{},{}", x1, y1, x2, y2}; x1 + border,
ctx.rectangle(x1,y1,x2,y2); y1 + border,
x2 - x1 - border,
y2 - y1 - border - border,
);
debug! {"Border clip: {},{},{},{}", x1, y1, x2, y2};
ctx.rectangle(x1, y1, x2, y2);
ctx.clip(); ctx.clip();
} }
pub fn input_box<T>(&self, x: T, y: T, input: &'a str) pub fn input_box<T>(&self, x: T, y: T, input: &'a str)
where T: Into<f64> where
T: Into<f64>,
{ {
let ctx = self.context; let ctx = self.context;
let (x, y): (f64, f64) = (x.into(), y.into()); let (x, y): (f64, f64) = (x.into(), y.into());
@ -75,28 +82,40 @@ impl<'a> Painter<'a> {
ctx.theme_text(&input, false, &[], &self.theme); ctx.theme_text(&input, false, &[], &self.theme);
} }
pub fn result_box<T>(&self, x: T, y: T, source: &'a str, result: &'a str, indices: &[usize], selected: bool) pub fn result_box<T>(
where T: Into<f64> &self,
x: T,
y: T,
source: &'a str,
result: &'a str,
indices: &[usize],
selected: bool,
) where
T: Into<f64>,
{ {
let ctx = self.context; let ctx = self.context;
let (x, y): (f64, f64) = (x.into(), y.into()); let (x, y): (f64, f64) = (x.into(), y.into());
// draw background box // draw background box
ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0); ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0);
if selected { ctx.theme_color_highlight(&self.theme) } if selected {
else { ctx.theme_color_base(&self.theme) }; ctx.theme_color_highlight(&self.theme)
} else {
ctx.theme_color_base(&self.theme)
};
ctx.set_operator(cairo::Operator::Source); ctx.set_operator(cairo::Operator::Source);
ctx.fill().unwrap(); ctx.fill().unwrap();
// draw text // draw text
ctx.move_to (x+0.2, y); ctx.move_to(x + 0.2, y);
ctx.theme_text(&source, selected, &[], &self.theme); ctx.theme_text(&source, selected, &[], &self.theme);
ctx.move_to (x+3.5, y); ctx.move_to(x + 3.5, y);
ctx.theme_text(&result, selected, indices, &self.theme); ctx.theme_text(&result, selected, indices, &self.theme);
} }
pub fn divider<T>(&self, x: T, y: T) pub fn divider<T>(&self, x: T, y: T)
where T: Into<f64> where
T: Into<f64>,
{ {
let ctx = self.context; let ctx = self.context;
let (x, y): (f64, f64) = (x.into(), y.into()); let (x, y): (f64, f64) = (x.into(), y.into());
@ -106,21 +125,24 @@ impl<'a> Painter<'a> {
ctx.line_to(self.clip_width(), y); ctx.line_to(self.clip_width(), y);
ctx.stroke().unwrap(); ctx.stroke().unwrap();
// ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width); // ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
// ctx.clip(); // ctx.clip();
} }
fn device_to_user(&self, point: f64) -> f64 { fn device_to_user(&self, point: f64) -> f64 {
self.context.device_to_user_distance(point, point).unwrap().0 self.context
.device_to_user_distance(point, point)
.unwrap()
.0
} }
fn clip_width(&self) -> f64 { fn clip_width(&self) -> f64 {
let (x1,_y1,x2,_y2) = self.context.clip_extents().unwrap(); let (x1, _y1, x2, _y2) = self.context.clip_extents().unwrap();
x2 - x1 x2 - x1
} }
fn clip_height(&self) -> f64 { fn clip_height(&self) -> f64 {
let (_x1,y1,_x2,y2) = self.context.clip_extents().unwrap(); let (_x1, y1, _x2, y2) = self.context.clip_extents().unwrap();
y2 - y1 y2 - y1
} }
} }

View file

@ -14,7 +14,7 @@ struct ColorScheme {
divider: Color, divider: Color,
text: Color, text: Color,
text_highlight: Color text_highlight: Color,
} }
pub struct Theme { pub struct Theme {
@ -22,7 +22,7 @@ pub struct Theme {
font: Font, font: Font,
border: f64, border: f64,
divider: f64 divider: f64,
} }
impl Theme { impl Theme {
@ -41,7 +41,7 @@ impl Default for Theme {
divider: Color(0.23, 0.05, 0.11, 1.0), divider: Color(0.23, 0.05, 0.11, 1.0),
text: Color(0.87, 0.95, 0.77, 1.0), text: Color(0.87, 0.95, 0.77, 1.0),
text_highlight: Color(0.6, 0.8, 0.4, 1.0) text_highlight: Color(0.6, 0.8, 0.4, 1.0),
}; };
Theme { Theme {
@ -49,7 +49,7 @@ impl Default for Theme {
font: Font("Sans Regular".into(), 13), font: Font("Sans Regular".into(), 13),
border: 2.0, border: 2.0,
divider: 3.0 divider: 3.0,
} }
} }
} }
@ -63,24 +63,23 @@ pub trait ThemedContextExt {
fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme); fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme);
fn device_to_user_point(&self, point: f64) -> f64; fn device_to_user_point(&self, point: f64) -> f64;
} }
impl ThemedContextExt for cairo::Context { impl ThemedContextExt for cairo::Context {
fn theme_color_base(&self, theme: &Theme) { fn theme_color_base(&self, theme: &Theme) {
let Color(r,g,b,a) = theme.colors.base; let Color(r, g, b, a) = theme.colors.base;
self.set_source_rgba(r,g,b,a); self.set_source_rgba(r, g, b, a);
} }
fn theme_color_highlight(&self, theme: &Theme) { fn theme_color_highlight(&self, theme: &Theme) {
let Color(r,g,b,a) = theme.colors.highlight; let Color(r, g, b, a) = theme.colors.highlight;
self.set_source_rgba(r,g,b,a); self.set_source_rgba(r, g, b, a);
} }
fn theme_border(&self, theme: &Theme) { fn theme_border(&self, theme: &Theme) {
let Color(r,g,b,a) = theme.colors.border; let Color(r, g, b, a) = theme.colors.border;
self.set_source_rgba(r,g,b,a); self.set_source_rgba(r, g, b, a);
let border_width = self.device_to_user_point(theme.border); let border_width = self.device_to_user_point(theme.border);
self.set_line_width(border_width); self.set_line_width(border_width);
} }
@ -91,7 +90,7 @@ impl ThemedContextExt for cairo::Context {
self.set_dash(&[self.device_to_user_point(3.0)], 0.0); self.set_dash(&[self.device_to_user_point(3.0)], 0.0);
let Color(r,g,b,a) = theme.colors.divider; let Color(r, g, b, a) = theme.colors.divider;
self.set_source_rgba(r, g, b, a); self.set_source_rgba(r, g, b, a);
} }
@ -102,16 +101,19 @@ impl ThemedContextExt for cairo::Context {
// gets totally confused by active cairo transformations // gets totally confused by active cairo transformations
// which then messes with font size, size of attributes, etc // which then messes with font size, size of attributes, etc
let Color(r,g,b,a) = if selected { theme.colors.text_highlight } let Color(r, g, b, a) = if selected {
else { theme.colors.text }; theme.colors.text_highlight
self.set_source_rgba(r,g,b,a); } else {
theme.colors.text
};
self.set_source_rgba(r, g, b, a);
let layout = pangocairo::create_layout(self).unwrap(); let layout = pangocairo::create_layout(self).unwrap();
pangocairo::update_layout(self, &layout); pangocairo::update_layout(self, &layout);
let attrlist = AttrList::new(); let attrlist = AttrList::new();
for i in indices { for i in indices {
let (start, end) = ((*i as u32), (*i as u32)+1); let (start, end) = ((*i as u32), (*i as u32) + 1);
let mut attr = Attribute::new_underline(pangocairo::pango::Underline::Single); let mut attr = Attribute::new_underline(pangocairo::pango::Underline::Single);
attr.set_start_index(start); attr.set_start_index(start);
attr.set_end_index(end); attr.set_end_index(end);
@ -121,18 +123,18 @@ impl ThemedContextExt for cairo::Context {
let mut font = FontDescription::default(); let mut font = FontDescription::default();
font.set_family(&theme.font.0); font.set_family(&theme.font.0);
font.set_size(theme.font.1*pango::SCALE); font.set_size(theme.font.1 * pango::SCALE);
layout.set_font_description(Some(&font)); layout.set_font_description(Some(&font));
layout.set_spacing(0); layout.set_spacing(0);
layout.set_text(text); layout.set_text(text);
let (_width,height) = layout.size(); let (_width, height) = layout.size();
let device_height = (height as f64)/(pango::SCALE as f64); let device_height = (height as f64) / (pango::SCALE as f64);
self.save().unwrap(); self.save().unwrap();
self.scale(30.0,30.0); self.scale(30.0, 30.0);
let cairo_user_height = self.device_to_user_point(device_height); let cairo_user_height = self.device_to_user_point(device_height);
let offset = (1.0 - cairo_user_height) / 2.0; let offset = (1.0 - cairo_user_height) / 2.0;
self.rel_move_to(0.0, offset); self.rel_move_to(0.0, offset);
@ -145,7 +147,7 @@ impl ThemedContextExt for cairo::Context {
fn device_to_user_point(&self, point: f64) -> f64 { fn device_to_user_point(&self, point: f64) -> f64 {
self.device_to_user(point, point) self.device_to_user(point, point)
.map(|(x,y)| if x>=y {x} else {y}) .map(|(x, y)| if x >= y { x } else { y })
.expect("Could not convert device to user space") .expect("Could not convert device to user space")
} }
} }

View file

@ -5,26 +5,103 @@ use log::debug;
use winit::{platform::unix::WindowExtUnix, window::Window}; use winit::{platform::unix::WindowExtUnix, window::Window};
use super::core::shared::Entry; use super::core::shared::Entry;
use super::painter; use super::painter;
pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
let context = make_context(&window);
context.scale(30.0, 30.0);
let painter = painter::Painter::new(&context);
painter.background();
painter.input_box(0, 0, input);
painter.divider(0, 1);
result.iter().enumerate().for_each(|(i, r)| {
let e = r.0;
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, selection == i);
});
}
pub fn redraw_quick(window: &Window, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
let context = make_context(&window);
context.scale(30.0, 30.0);
let painter = painter::Painter::new(&context);
painter.border_clip();
result.iter().enumerate().for_each(|(i, r)| {
let e = r.0;
// clear first and last as these may produce artifacts otherwise
if i == 0 {
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false)
}
if i == result.len() - 1 {
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false)
}
// clear boxes around selection as these could be the old selection
if selection > 0 && i == (selection - 1) {
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false)
}
if i == (selection + 1) {
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false)
}
// mark selection, note that this negates any unmarking above in case they are the same
if i == selection {
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, true)
}
});
}
pub fn draw_actions(window: &Window, actions: Vec<String>, input: &str) {
let context = make_context(&window);
context.scale(30.0, 30.0);
let painter = painter::Painter::new(&context);
painter.background();
painter.input_box(0, 0, input);
painter.divider(0, 1);
actions.iter().enumerate().for_each(|(i, r)| {
painter.result_box(
0,
1 + (i as u32),
&usize::to_string(&(i + 1)),
r,
&[],
false,
);
});
}
fn get_visual_type<T>(mut connection: T, window_id: u32) -> xcb::Visualtype fn get_visual_type<T>(mut connection: T, window_id: u32) -> xcb::Visualtype
where T: BorrowMut<*mut c_void> where
T: BorrowMut<*mut c_void>,
{ {
let xcb_conn = unsafe { xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t) }; let xcb_conn = unsafe {
xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t)
};
let window_visualid = xcb::get_window_attributes(&xcb_conn, window_id) let window_visualid = xcb::get_window_attributes(&xcb_conn, window_id)
.get_reply() .get_reply()
.expect("Could not fetch attributes for window") .expect("Could not fetch attributes for window")
.visual(); .visual();
debug!{"Found visualid {} for window", window_visualid} debug! {"Found visualid {} for window", window_visualid}
debug!{"Trying to map visualid to visualtype {}", window_visualid} debug! {"Trying to map visualid to visualtype {}", window_visualid}
let visualtype = xcb_conn.get_setup().roots() let visualtype = xcb_conn
.get_setup()
.roots()
.flat_map(|screen| screen.allowed_depths()) .flat_map(|screen| screen.allowed_depths())
.flat_map(|depth| depth.visuals()) .flat_map(|depth| depth.visuals())
.find(|visualtype| {visualtype.visual_id() == window_visualid}) .find(|visualtype| visualtype.visual_id() == window_visualid)
.expect("Could not match visualid to visualtype"); .expect("Could not match visualid to visualtype");
// xcb::Connection calls disconnect on the underlying xcb_connection_t // xcb::Connection calls disconnect on the underlying xcb_connection_t
@ -37,20 +114,18 @@ where T: BorrowMut<*mut c_void>
visualtype visualtype
} }
fn make_context(window: &Window) -> cairo::Context fn make_context(window: &Window) -> cairo::Context {
{
let winit_xcb_conn = window let winit_xcb_conn = window
.xcb_connection() .xcb_connection()
.expect("Could not get connection from window"); .expect("Could not get connection from window");
let xlib_window_id = window let xlib_window_id = window.xlib_window().expect("Could not get xlib window");
.xlib_window()
.expect("Could not get xlib window");
let visual_type = unsafe { let visual_type = unsafe {
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base; let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base;
cairo::XCBVisualType::from_raw_none( cairo::XCBVisualType::from_raw_none(
&mut visual_type as *mut xcb::ffi::xcb_visualtype_t as *mut cairo::ffi::xcb_visualtype_t &mut visual_type as *mut xcb::ffi::xcb_visualtype_t
as *mut cairo::ffi::xcb_visualtype_t,
) )
}; };
@ -66,69 +141,8 @@ fn make_context(window: &Window) -> cairo::Context
&visual_type, &visual_type,
window.inner_size().width as i32, window.inner_size().width as i32,
window.inner_size().height as i32, window.inner_size().height as i32,
).expect("Could not create drawing surface"); )
.expect("Could not create drawing surface");
cairo::Context::new(&surface).expect("Could not create drawing context") cairo::Context::new(&surface).expect("Could not create drawing context")
} }
pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize)
{
let context = make_context(&window);
context.scale(30.0, 30.0);
let painter = painter::Painter::new(&context);
painter.background();
painter.input_box(0, 0, input);
painter.divider(0, 1);
result.iter().enumerate()
.for_each(|(i, r)| {
let e = r.0;
painter.result_box(0, 1+(i as u32), &e.source, &e.name, &r.1, selection==i);
});
}
pub fn redraw_quick(window: &Window, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
let context = make_context(&window);
context.scale(30.0, 30.0);
let painter = painter::Painter::new(&context);
painter.border_clip();
result.iter().enumerate()
.for_each(|(i, r)| {
let e = r.0;
// clear first and last as these may produce artifacts otherwise
if i == 0 { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &r.1, false) }
if i == result.len()-1 { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &r.1, false) }
// clear boxes around selection as these could be the old selection
if selection > 0 && i == (selection-1) { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &r.1, false) }
if i == (selection+1) { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &r.1, false) }
// mark selection, note that this negates any unmarking above in case they are the same
if i == selection { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &r.1, true) }
});
}
pub fn draw_actions(window: &Window, actions: Vec<String>, input: &str)
{
let context = make_context(&window);
context.scale(30.0, 30.0);
let painter = painter::Painter::new(&context);
painter.background();
painter.input_box(0, 0, input);
painter.divider(0, 1);
actions.iter().enumerate()
.for_each(|(i, r)| {
painter.result_box(0, 1+(i as u32), &usize::to_string(&(i+1)), r, &[], false);
});
}