Run cargo format
This commit is contained in:
parent
3b7bff5bd7
commit
a34a6a2ed4
20 changed files with 625 additions and 392 deletions
|
@ -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.
|
||||
|
||||
|
|
15
default.toml
15
default.toml
|
@ -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]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub mod shared;
|
||||
pub mod roftl;
|
||||
pub mod settings;
|
||||
pub mod shared;
|
||||
|
||||
use super::matcher;
|
||||
use super::sources;
|
||||
|
|
|
@ -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()
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
@ -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,20 +204,35 @@ 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};
|
||||
|
|
|
@ -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()
|
||||
config.merge(
|
||||
config::Environment::new()
|
||||
.prefix("roftl")
|
||||
.separator("_")
|
||||
.ignore_empty(true))?;
|
||||
.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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
125
src/main.rs
125
src/main.rs
|
@ -1,17 +1,19 @@
|
|||
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();
|
||||
|
@ -66,48 +68,52 @@ struct RoftlLoop {
|
|||
}
|
||||
|
||||
impl RoftlLoop {
|
||||
fn new(roftl: Roftl, window: Window) -> Self
|
||||
{
|
||||
RoftlLoop{input_buffer: String::default(), input_changed: true, selection: 0,
|
||||
fn new(roftl: Roftl, window: Window) -> Self {
|
||||
RoftlLoop {
|
||||
input_buffer: String::default(),
|
||||
input_changed: true,
|
||||
selection: 0,
|
||||
mode: Mode::Selection,
|
||||
roftl, window}
|
||||
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}
|
||||
|
@ -122,8 +128,11 @@ 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}
|
||||
|
@ -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}
|
||||
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
|
||||
|
@ -204,7 +216,9 @@ impl RoftlLoop {
|
|||
// Ctrl+p
|
||||
c if c == char::from(0x10) => {
|
||||
debug! {"Received previous"}
|
||||
if self.selection != 0 { self.selection -= 1 }
|
||||
if self.selection != 0 {
|
||||
self.selection -= 1
|
||||
}
|
||||
self.window.request_redraw();
|
||||
ControlFlow::Wait
|
||||
}
|
||||
|
@ -213,8 +227,14 @@ impl RoftlLoop {
|
|||
c if c == char::from(0x09) => {
|
||||
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
|
||||
|
@ -241,22 +261,22 @@ impl RoftlLoop {
|
|||
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"}
|
||||
self.roftl.exec_action(self.selection, 1);
|
||||
ControlFlow::Exit
|
||||
},
|
||||
}
|
||||
'3' => {
|
||||
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"}
|
||||
self.roftl.exec_action(self.selection, 3);
|
||||
ControlFlow::Exit
|
||||
},
|
||||
}
|
||||
|
||||
'q' | 'Q' => ControlFlow::Exit,
|
||||
|
||||
|
@ -267,18 +287,23 @@ impl RoftlLoop {
|
|||
c if c == char::from(0x09) => {
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ impl Matcher for FlxMatcher {
|
|||
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))
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,4 +11,3 @@ pub use fuse::FuseMatcher;
|
|||
|
||||
mod flx;
|
||||
pub use flx::FlxMatcher;
|
||||
|
||||
|
|
|
@ -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()
|
||||
matcher: SkimMatcherV2::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ 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
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>) {
|
||||
|
@ -50,7 +53,6 @@ impl App {
|
|||
command.args(&args);
|
||||
let _ = run_bg(command);
|
||||
} else {
|
||||
|
||||
debug!("Running `{} {}`", cmd, args.join(" "));
|
||||
let mut command = Command::new(&cmd);
|
||||
command.args(&args);
|
||||
|
@ -59,18 +61,15 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
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,33 +77,37 @@ 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 {
|
||||
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))
|
||||
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>
|
||||
{
|
||||
pub fn new() -> Box<Apps> {
|
||||
Box::new(Apps { entries: vec![] })
|
||||
}
|
||||
}
|
||||
|
@ -112,9 +115,12 @@ impl Apps {
|
|||
fn strip_entry_args(exec: &str) -> String {
|
||||
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}
|
||||
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,28 +153,24 @@ 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}
|
||||
|
|
|
@ -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};
|
||||
|
@ -25,18 +29,18 @@ 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 {
|
||||
entries.push(Entry {
|
||||
source: self.name(),
|
||||
name: script.file_stem().unwrap().to_str().unwrap().into(),
|
||||
description: "".into(),
|
||||
identifier: idx as u64
|
||||
identifier: idx as u64,
|
||||
});
|
||||
}
|
||||
self.scripts = scripts;
|
||||
|
@ -44,7 +48,6 @@ impl Source for ShellHost {
|
|||
}
|
||||
|
||||
fn exec_action(&self, entry: &Entry, action: u8) {
|
||||
|
||||
let script: &PathBuf = self.scripts.get(entry.identifier as usize).unwrap();
|
||||
debug! {"Got script {:?}", script};
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()?;
|
||||
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> {
|
||||
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()?;
|
||||
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,7 +160,12 @@ 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;
|
||||
|
@ -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(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
@ -37,8 +39,7 @@ impl<'a> Painter<'a> {
|
|||
self.border_clip();
|
||||
}
|
||||
|
||||
pub fn border_clip(&self)
|
||||
{
|
||||
pub fn border_clip(&self) {
|
||||
let ctx = self.context;
|
||||
|
||||
ctx.reset_clip();
|
||||
|
@ -53,14 +54,20 @@ impl<'a> Painter<'a> {
|
|||
|
||||
// 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);
|
||||
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,16 +82,27 @@ 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();
|
||||
|
||||
|
@ -96,7 +114,8 @@ impl<'a> Painter<'a> {
|
|||
}
|
||||
|
||||
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());
|
||||
|
@ -111,7 +130,10 @@ impl<'a> Painter<'a> {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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,7 +63,6 @@ pub trait ThemedContextExt {
|
|||
|
||||
fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme);
|
||||
|
||||
|
||||
fn device_to_user_point(&self, point: f64) -> f64;
|
||||
}
|
||||
|
||||
|
@ -102,8 +101,11 @@ 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 };
|
||||
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();
|
||||
|
|
162
src/ui/ui.rs
162
src/ui/ui.rs
|
@ -5,13 +5,88 @@ 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()
|
||||
|
@ -21,10 +96,12 @@ where T: BorrowMut<*mut c_void>
|
|||
debug! {"Found visualid {} for window", 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(|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);
|
||||
});
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue