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
> In the beginning there was Rofi. Rofi was Fast. And Fast was Rofi.
> In the beginning there was Rofi. Rofi was good.
>
> Then Light.
@ -11,4 +11,4 @@ just found recently.
Anyways.
They found an even older git repository in an _ante-prehistoric_ cave. If your
mind is not blown away by now already: Here's a mirror.
mind is not blown away by now already: Here's a mirror.

View file

@ -6,9 +6,22 @@ log_level = "trace"
"t" = "terminal"
[sources.primary]
matcher = "Prefix"
matcher = "Flex"
source = "Windows"
[sources.additional]
matcher = "Flex"
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 settings;
pub mod shared;
use super::matcher;
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 rayon::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use super::matcher::PrefixMatcher;
use super::shared::Entry;
use super::shared::Matcher;
use super::shared::Source;
use super::shared::Entry;
pub type SourceRef = Box<dyn Source>;
pub type MatcherRef = Box<dyn Matcher>;
@ -26,7 +25,7 @@ pub struct Roftl {
primary_matcher: MatcherRef,
entries: Vec<Entry>,
narrow_map: Vec<usize>,
completions: HashMap<String, String>
completions: HashMap<String, String>,
}
impl Roftl {
@ -39,18 +38,23 @@ impl Roftl {
primary_matcher: PrefixMatcher::new(),
entries: vec![],
narrow_map: vec![],
completions: HashMap::default()
completions: HashMap::default(),
}
}
pub fn with_primary_source(mut self, source: SourceRef) -> Self {
self.primary_source = Some(source);
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()}
}
@ -79,7 +83,8 @@ impl Roftl {
}
fn source_primary(&mut self) {
self.entries = self.primary_source
self.entries = self
.primary_source
.par_iter_mut()
.flat_map(|s| s.entries())
.collect();
@ -93,12 +98,18 @@ impl Roftl {
let sources = self.sources.clone();
std::thread::spawn(move || {
let entries: Vec<Entry> = sources.write().unwrap()
.par_iter_mut()
.flat_map(|s| s.entries())
.collect();
let entries: Vec<Entry> = sources
.write()
.unwrap()
.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();
});
}
@ -110,7 +121,7 @@ impl Roftl {
debug!("Processing {} additional entries", entries.len());
self.entries.append(&mut entries);
} 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
let completions = &self.completions;
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()
.enumerate()
.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
.unwrap_or(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();
@ -141,20 +158,22 @@ impl Roftl {
let input = input.strip_prefix(",").unwrap_or(input);
let matcher = &self.matcher;
scored_entries = self.entries
scored_entries = self
.entries
.par_iter()
.enumerate()
.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);
return match_result.map(|(score, indices)| (score, idx, entry, indices) )
return match_result.map(|(score, indices)| (score, idx, entry, indices));
})
.collect();
}
scored_entries
.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
scored_entries.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
self.narrow_map.clear();
for se in &scored_entries {
@ -171,7 +190,13 @@ impl Roftl {
let (entry, source) = self.find_selection(selection_id);
match source {
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);
match source {
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) {
let (entry, source) = self.find_selection(selection_id);
match source {
usize::MAX => self.primary_source.as_ref().unwrap().exec_action(entry, action),
_ => self.sources.read().unwrap().get(source).unwrap().exec_action(entry, action)
usize::MAX => self
.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 = &self.entries[entry_id];
debug!{"Got entry {:?} for id {} ", entry, entry_id};
debug! {"Got entry {:?} for id {} ", entry, entry_id};
match self.primary_source {
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::ConfigError;
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)]
pub struct Theme {
color_scheme: ColorScheme,
font: (String, i32),
border: f64,
divider: f64
divider: f64,
}
#[derive(Debug, Deserialize)]
pub enum Matcher {
Prefix, Fuse, Flex, Skim
Prefix,
Fuse,
Flex,
Skim,
}
impl Matcher {
@ -20,14 +40,17 @@ impl Matcher {
Matcher::Prefix => super::matcher::PrefixMatcher::new(),
Matcher::Fuse => super::matcher::FuseMatcher::new(),
Matcher::Flex => super::matcher::FlxMatcher::new(),
Matcher::Skim => super::matcher::SkimMatcher::new()
Matcher::Skim => super::matcher::SkimMatcher::new(),
}
}
}
#[derive(Debug, Deserialize)]
pub enum Source {
Apps, Shell, Windows, Test
Apps,
Shell,
Windows,
Test,
}
impl Source {
@ -36,7 +59,7 @@ impl Source {
Source::Apps => super::sources::Apps::new(),
Source::Shell => super::sources::ShellHost::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)]
pub struct SourceConfig<T> {
matcher: Matcher,
source: T
source: T,
}
#[derive(Debug, Deserialize)]
@ -56,16 +79,20 @@ pub struct Sources {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase", remote = "log::Level")]
enum LogLevelDef {
Error, Warn, Info, Debug, Trace,
Error,
Warn,
Info,
Debug,
Trace,
}
#[derive(Debug, Deserialize)]
pub struct Settings {
#[serde(with = "LogLevelDef")]
log_level: log::Level,
completions: HashMap<String, String>,
sources: Sources,
theme: Theme,
}
impl Settings {
@ -74,10 +101,12 @@ impl Settings {
config.merge(config::File::with_name("default"))?;
config.merge(config::Environment::new()
.prefix("roftl")
.separator("_")
.ignore_empty(true))?;
config.merge(
config::Environment::new()
.prefix("roftl")
.separator("_")
.ignore_empty(true),
)?;
config.try_into()
}
@ -95,7 +124,9 @@ impl Settings {
}
pub fn sources(&self) -> Vec<Box<dyn super::shared::Source>> {
self.sources.additional.source
self.sources
.additional
.source
.iter()
.map(|s| s.init())
.collect()

View file

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

View file

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

View file

@ -7,19 +7,17 @@ pub struct FlxMatcher;
impl FlxMatcher {
pub fn new() -> Box<Self> {
Box::new(FlxMatcher{})
Box::new(FlxMatcher {})
}
}
impl Matcher for FlxMatcher {
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);
res.and_then(|score| {
let s = score.score as f64;
let i = score.indices.into_iter()
.map(|idx| idx as usize)
.collect();
let i = score.indices.into_iter().map(|idx| idx as usize).collect();
Some((s, i))
})

View file

@ -5,18 +5,20 @@ use fuse_rust::Fuse;
use super::core::shared::Matcher;
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()
}
pub struct FuseMatcher {
matcher: Fuse
matcher: Fuse,
}
impl FuseMatcher {
pub fn new() -> Box<Self> {
Box::new(FuseMatcher{
matcher: Fuse::default()
Box::new(FuseMatcher {
matcher: Fuse::default(),
})
}
}
@ -25,9 +27,7 @@ impl Matcher for FuseMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
match self.matcher.search_text_in_string(needle, haystack) {
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;
pub use flx::FlxMatcher;

View file

@ -4,7 +4,7 @@ pub struct PrefixMatcher;
impl PrefixMatcher {
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;
pub struct SkimMatcher {
matcher: SkimMatcherV2
matcher: SkimMatcherV2,
}
impl SkimMatcher {
pub fn new() -> Box<Self> {
Box::new(SkimMatcher{
matcher: SkimMatcherV2::default()
Box::new(SkimMatcher {
matcher: SkimMatcherV2::default(),
})
}
}
@ -17,8 +17,8 @@ impl SkimMatcher {
impl Matcher for SkimMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
match self.matcher.fuzzy_indices(haystack, needle) {
Some((score, indices)) => Some(((i64::MAX as f64/score as f64), indices)),
None => None
Some((score, indices)) => Some(((i64::MAX as f64 / score as f64), indices)),
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 log::{debug, error, trace};
use walkdir::WalkDir;
use nix::unistd::{getpid, setpgid};
use walkdir::WalkDir;
use super::core::shared::{Entry, Source};
@ -11,7 +14,7 @@ use super::core::shared::{Entry, Source};
struct App {
name: String,
command: String,
terminal: bool
terminal: bool,
}
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(|| {
let pid = getpid();
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(())
});
}
debug!{"Running command {:?}", command}
debug! {"Running command {:?}", command}
command.spawn().unwrap();
}
@ -50,27 +53,23 @@ impl App {
command.args(&args);
let _ = run_bg(command);
} else {
debug!("Running `{} {}`", cmd, args.join(" "));
let mut command = Command::new(&cmd);
command.args(&args);
let _ = run_bg(command);
debug!("Running `{} {}`", cmd, args.join(" "));
let mut command = Command::new(&cmd);
command.args(&args);
let _ = run_bg(command);
}
}
}
impl From<&App> for Entry
{
impl From<&App> for Entry {
fn from(app: &App) -> Self {
Entry {
name: app.name.clone(),
description: "".into(),
source: "apps",
identifier: 0
identifier: 0,
}
}
}
#[derive(Default, Debug)]
@ -78,45 +77,52 @@ struct AppBuilder<'a> {
name: Option<&'a str>,
exec: Option<&'a str>,
hidden: Option<&'a str>,
terminal: Option<&'a str>
terminal: Option<&'a str>,
}
impl<'a> AppBuilder<'a>
{
fn build(&self) -> Option<App>
{
if self.name.is_none() { return None }
if self.exec.is_none() { return None }
if self.hidden.is_some() { return None }
impl<'a> AppBuilder<'a> {
fn build(&self) -> Option<App> {
if self.name.is_none() {
return None;
}
if self.exec.is_none() {
return None;
}
if self.hidden.is_some() {
return None;
}
Some (
App {
name: self.name.unwrap().into(),
command: strip_entry_args(self.exec.unwrap()).into(),
terminal: self.terminal.map_or(false, |term| term.parse().unwrap_or(false))
})
Some(App {
name: self.name.unwrap().into(),
command: strip_entry_args(self.exec.unwrap()).into(),
terminal: self
.terminal
.map_or(false, |term| term.parse().unwrap_or(false)),
})
}
}
pub struct Apps {
entries: Vec<App>
entries: Vec<App>,
}
impl Apps {
pub fn new() -> Box<Apps>
{
Box::new(Apps{entries: vec![]})
pub fn new() -> Box<Apps> {
Box::new(Apps { entries: vec![] })
}
}
fn strip_entry_args(exec: &str) -> String {
trace!{"Stripping {}", exec}
trace! {"Stripping {}", exec}
let iter = exec.split(' ');
let result = iter.filter(|item| !item.starts_with('%'))
let result = iter
.filter(|item| !item.starts_with('%'))
.collect::<Vec<&str>>()
.join(" ");
.join(" ")
.trim_matches('"')
.into();
trace!{"Stripped into {}", result}
trace! {"Stripped into {}", result}
result
}
@ -130,7 +136,7 @@ impl Source for Apps {
"/usr/share/applications/",
"/home/armin/.local/share/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
@ -147,41 +153,37 @@ impl Source for Apps {
let mut app_builder = AppBuilder::default();
let section = entry.section("Desktop Entry");
for attr in section.attrs()
{
match attr.name
{
for attr in section.attrs() {
match attr.name {
"Name" => app_builder.name = attr.value,
"Exec" => app_builder.exec = attr.value,
"NoDisplay" => app_builder.hidden = attr.value,
"Hidden" => app_builder.hidden = 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();
entry.identifier = idx;
idx += 1;
entries.push(entry);
self.entries.push(app);
}
}
trace!{"Got desktop entries: {:?}", entries}
trace! {"Got desktop entries: {:?}", entries}
entries
}
fn exec_action(&self, entry: &Entry, action: u8) {
debug!{"Got desktop entry {:?} for action", entry}
debug!{"Desktop entry has id {}", entry.identifier}
trace!{"Apps has entries {:?}", self.entries}
debug! {"Got desktop entry {:?} for action", entry}
debug! {"Desktop entry has id {}", entry.identifier}
trace! {"Apps has entries {:?}", self.entries}
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();
}
}

View file

@ -1,6 +1,10 @@
use std::{os::unix::prelude::CommandExt, path::PathBuf, process::{Command, Stdio}};
use log::{debug, error};
use nix::unistd::{getpid, setpgid};
use std::{
os::unix::prelude::CommandExt,
path::PathBuf,
process::{Command, Stdio},
};
use walkdir::WalkDir;
use super::core::shared::{Entry, Source};
@ -11,7 +15,7 @@ pub struct ShellHost {
impl 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";
// 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())
.collect();
let mut entries: Vec<Entry> = vec![];
for (idx, script) in scripts.iter().enumerate() {
entries.push(
Entry {
source: self.name(),
name: script.file_stem().unwrap().to_str().unwrap().into(),
description: "".into(),
identifier: idx as u64
});
entries.push(Entry {
source: self.name(),
name: script.file_stem().unwrap().to_str().unwrap().into(),
description: "".into(),
identifier: idx as u64,
});
}
self.scripts = scripts;
entries
}
fn exec_action(&self, entry: &Entry, action: u8) {
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 args = vec!["-x", script.to_str().unwrap()];
command.args(&args);
let _ = run_bg(command);
debug!{"Executed {:?}", script};
debug! {"Executed {:?}", script};
}
}
@ -63,12 +66,12 @@ pub fn run_bg(mut command: Command) {
command.pre_exec(|| {
let pid = getpid();
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(())
});
}
debug!{"Running command {:?}", command}
debug! {"Running command {:?}", command}
command.spawn().unwrap();
}

View file

@ -44,7 +44,8 @@ impl Source for TestSource {
fn actions(&self, _entry: &Entry) -> Vec<String> {
["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;
pub struct Window {
action_data: HashMap<u64, (u32, i32)>
action_data: HashMap<u64, (u32, i32)>,
}
impl 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()?;
for atom in wm_type_reply.atoms() {
@ -28,48 +34,95 @@ impl Window {
Ok(false)
}
fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into())
fn window_category(
&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> {
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 active_window = ewmh::get_active_window(con, screen).get_reply()?;
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::set_active_window(con, screen, window).request_check().unwrap();
ewmh::request_change_active_window(
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);
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_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con);
let action_atom = ewmh::STATE_TOGGLE;
debug!{"Toggle maximize for {}", window}
ewmh::request_change_wm_state(con, screen, window, action_atom, max_horz_atom, max_vert_atom, 0).request_check()?;
debug! {"Toggle maximize for {}", window}
ewmh::request_change_wm_state(
con,
screen,
window,
action_atom,
max_horz_atom,
max_vert_atom,
0,
)
.request_check()?;
Ok(())
}
fn close_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> {
debug!{"Toggle maximize for {}", window}
fn close_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()
}
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 action_atom = ewmh::STATE_TOGGLE;
debug!{"Toggle hidden for {}", window}
ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0).request_check()?;
debug! {"Toggle hidden for {}", window}
ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0)
.request_check()?;
Ok(())
}
@ -81,10 +134,15 @@ impl Source for Window {
}
fn entries(&mut self) -> Vec<Entry> {
let (xcb_conn, screen_num) = 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 (xcb_conn, screen_num) =
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 mut entries: Vec<Entry> = vec![];
@ -92,7 +150,7 @@ impl Source for Window {
let mut count: u64 = 0;
for w in windows {
match self.is_normal_window(&ewmh_conn, *w) {
Ok(true) => {},
Ok(true) => {}
Ok(false) => continue,
Err(_err) => {}
}
@ -102,10 +160,15 @@ impl Source for Window {
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));
count+=1;
count += 1;
}
entries
@ -117,36 +180,54 @@ impl Source for Window {
match action {
0 => {
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 ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
let (xcb_conn, _screen_num) =
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()
},
}
1 => {
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 ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
self.toggle_maximize_window(&ewmh_conn, window, screen).unwrap();
let (xcb_conn, _screen_num) =
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_maximize_window(&ewmh_conn, window, screen)
.unwrap();
self.switch_window(&ewmh_conn, window, screen).unwrap()
},
}
2 => {
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 ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
let (xcb_conn, _screen_num) =
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();
},
}
3 => {
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 ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
let (xcb_conn, _screen_num) =
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();
},
_ => panic!{"Unknown action {:?}", action}
}
_ => panic! {"Unknown action {:?}", action},
}
}
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;
pub use ui::draw;
pub use ui::redraw_quick;
pub use ui::draw_actions;
pub use ui::redraw_quick;
mod painter;
mod theme;

View file

@ -9,11 +9,13 @@ pub struct Painter<'a> {
impl<'a> Painter<'a> {
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;
// 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");
// draw the border
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap();
debug!{"Border width: {}", self.theme.border(ctx)};
let border = self.theme.border(ctx)/2.0;
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border);
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2};
ctx.rectangle(x1,y1,x2-x1,y2-y1);
let (x1, y1, x2, y2) = ctx.clip_extents().unwrap();
debug! {"Border width: {}", self.theme.border(ctx)};
let border = self.theme.border(ctx) / 2.0;
let (x1, y1, x2, y2) = (x1 + border, y1 + border, x2 - x1 - border, y2 - y1 - border);
debug! {"Border rect: {},{},{},{}", x1, y1, x2, y2};
ctx.rectangle(x1, y1, x2 - x1, y2 - y1);
ctx.theme_border(&self.theme);
ctx.stroke().unwrap();
self.border_clip();
}
pub fn border_clip(&self)
{
pub fn border_clip(&self) {
let ctx = self.context;
ctx.reset_clip();
// clip any future content so the border stays alive
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap(); // the path of the clip
// region, i.e. the surface
let (x1, y1, x2, y2) = ctx.clip_extents().unwrap(); // the path of the clip
// 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
// that half of the border is outside
// the clip_extents
// let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border);
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2};
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border-border);
debug!{"Border clip: {},{},{},{}", x1, y1, x2, y2};
ctx.rectangle(x1,y1,x2,y2);
// let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border);
debug! {"Border rect: {},{},{},{}", x1, y1, x2, y2};
let (x1, y1, x2, y2) = (
x1 + border,
y1 + border,
x2 - x1 - border,
y2 - y1 - border - border,
);
debug! {"Border clip: {},{},{},{}", x1, y1, x2, y2};
ctx.rectangle(x1, y1, x2, y2);
ctx.clip();
}
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 (x, y): (f64, f64) = (x.into(), y.into());
@ -75,28 +82,40 @@ impl<'a> Painter<'a> {
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)
where T: Into<f64>
pub fn result_box<T>(
&self,
x: T,
y: T,
source: &'a str,
result: &'a str,
indices: &[usize],
selected: bool,
) where
T: Into<f64>,
{
let ctx = self.context;
let (x, y): (f64, f64) = (x.into(), y.into());
// draw background box
ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0);
if selected { ctx.theme_color_highlight(&self.theme) }
else { ctx.theme_color_base(&self.theme) };
if selected {
ctx.theme_color_highlight(&self.theme)
} else {
ctx.theme_color_base(&self.theme)
};
ctx.set_operator(cairo::Operator::Source);
ctx.fill().unwrap();
// draw text
ctx.move_to (x+0.2, y);
ctx.move_to(x + 0.2, y);
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);
}
pub fn divider<T>(&self, x: T, y: T)
where T: Into<f64>
where
T: Into<f64>,
{
let ctx = self.context;
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.stroke().unwrap();
// ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
// ctx.clip();
// ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
// ctx.clip();
}
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 {
let (x1,_y1,x2,_y2) = self.context.clip_extents().unwrap();
let (x1, _y1, x2, _y2) = self.context.clip_extents().unwrap();
x2 - x1
}
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
}
}

View file

@ -14,7 +14,7 @@ struct ColorScheme {
divider: Color,
text: Color,
text_highlight: Color
text_highlight: Color,
}
pub struct Theme {
@ -22,7 +22,7 @@ pub struct Theme {
font: Font,
border: f64,
divider: f64
divider: f64,
}
impl Theme {
@ -41,7 +41,7 @@ impl Default for Theme {
divider: Color(0.23, 0.05, 0.11, 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 {
@ -49,7 +49,7 @@ impl Default for Theme {
font: Font("Sans Regular".into(), 13),
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 device_to_user_point(&self, point: f64) -> f64;
}
impl ThemedContextExt for cairo::Context {
fn theme_color_base(&self, theme: &Theme) {
let Color(r,g,b,a) = theme.colors.base;
self.set_source_rgba(r,g,b,a);
let Color(r, g, b, a) = theme.colors.base;
self.set_source_rgba(r, g, b, a);
}
fn theme_color_highlight(&self, theme: &Theme) {
let Color(r,g,b,a) = theme.colors.highlight;
self.set_source_rgba(r,g,b,a);
let Color(r, g, b, a) = theme.colors.highlight;
self.set_source_rgba(r, g, b, a);
}
fn theme_border(&self, theme: &Theme) {
let Color(r,g,b,a) = theme.colors.border;
self.set_source_rgba(r,g,b,a);
let Color(r, g, b, a) = theme.colors.border;
self.set_source_rgba(r, g, b, a);
let border_width = self.device_to_user_point(theme.border);
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);
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);
}
@ -102,16 +101,19 @@ impl ThemedContextExt for cairo::Context {
// gets totally confused by active cairo transformations
// which then messes with font size, size of attributes, etc
let Color(r,g,b,a) = if selected { theme.colors.text_highlight }
else { theme.colors.text };
self.set_source_rgba(r,g,b,a);
let Color(r, g, b, a) = if selected {
theme.colors.text_highlight
} else {
theme.colors.text
};
self.set_source_rgba(r, g, b, a);
let layout = pangocairo::create_layout(self).unwrap();
pangocairo::update_layout(self, &layout);
let attrlist = AttrList::new();
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);
attr.set_start_index(start);
attr.set_end_index(end);
@ -121,18 +123,18 @@ impl ThemedContextExt for cairo::Context {
let mut font = FontDescription::default();
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_spacing(0);
layout.set_text(text);
let (_width,height) = layout.size();
let device_height = (height as f64)/(pango::SCALE as f64);
let (_width, height) = layout.size();
let device_height = (height as f64) / (pango::SCALE as f64);
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 offset = (1.0 - cairo_user_height) / 2.0;
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 {
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")
}
}

View file

@ -5,26 +5,103 @@ use log::debug;
use winit::{platform::unix::WindowExtUnix, window::Window};
use super::core::shared::Entry;
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
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)
.get_reply()
.expect("Could not fetch attributes for window")
.visual();
debug!{"Found visualid {} for window", window_visualid}
debug! {"Found visualid {} for window", window_visualid}
debug!{"Trying to map visualid to visualtype {}", window_visualid}
let visualtype = xcb_conn.get_setup().roots()
debug! {"Trying to map visualid to visualtype {}", window_visualid}
let visualtype = xcb_conn
.get_setup()
.roots()
.flat_map(|screen| screen.allowed_depths())
.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");
// xcb::Connection calls disconnect on the underlying xcb_connection_t
@ -37,20 +114,18 @@ where T: BorrowMut<*mut c_void>
visualtype
}
fn make_context(window: &Window) -> cairo::Context
{
fn make_context(window: &Window) -> cairo::Context {
let winit_xcb_conn = window
.xcb_connection()
.expect("Could not get connection from window");
let xlib_window_id = window
.xlib_window()
.expect("Could not get xlib window");
let xlib_window_id = window.xlib_window().expect("Could not get xlib window");
let visual_type = unsafe {
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base;
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,
window.inner_size().width 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")
}
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);
});
}