From 8e38a59432e9b8b5251158553fed8b47b981797d Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 17 Oct 2021 13:46:56 +0200 Subject: [PATCH] Sort entries by score, ui widgets --- src/roftl.rs | 28 ++++++-- src/sources/test.rs | 2 +- src/ui.rs | 167 -------------------------------------------- src/ui/mod.rs | 4 ++ src/ui/painter.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++ src/ui/ui.rs | 91 ++++++++++++++++++++++++ 6 files changed, 284 insertions(+), 175 deletions(-) delete mode 100644 src/ui.rs create mode 100644 src/ui/mod.rs create mode 100644 src/ui/painter.rs create mode 100644 src/ui/ui.rs diff --git a/src/roftl.rs b/src/roftl.rs index e84ef9c..f49ee58 100644 --- a/src/roftl.rs +++ b/src/roftl.rs @@ -1,6 +1,7 @@ -use std::{collections::HashMap, sync::{Arc, Mutex, atomic::{AtomicUsize, Ordering}}}; +use std::{any::type_name, collections::HashMap, sync::{Arc, Mutex, atomic::{AtomicUsize, Ordering}}}; -use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; +use log::debug; +use rayon::{iter::{IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}, slice::ParallelSliceMut}; use super::matcher::PrefixMatcher; @@ -75,14 +76,27 @@ impl Roftl { let count = Arc::new(AtomicUsize::new(0)); self.narrow_map.lock().unwrap().clear(); - self.entries + let mut scored_entries: Vec<(f64, usize, &Entry, Vec)> = self.entries .par_iter() .enumerate() - .filter(|(_idx, e)| self.matcher.try_match(&e.name, input).is_some()) - .map(move |(idx, e)| { - self.narrow_map.lock().unwrap().insert(count.fetch_add(1, Ordering::Relaxed), idx); - e + .filter_map(|(idx, entry)| { + let match_result = self.matcher.try_match(&entry.name, input); + 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_iter() + .for_each(|se| { + self.narrow_map.lock().unwrap().insert(count.fetch_add(1, Ordering::Relaxed), se.1); + }); + + scored_entries + .par_iter() + .map(|se| se.2) .collect() } diff --git a/src/sources/test.rs b/src/sources/test.rs index 2bc2463..34eb13f 100644 --- a/src/sources/test.rs +++ b/src/sources/test.rs @@ -8,7 +8,7 @@ impl TestSource { pub fn new(s: &str) -> Box { let mut ts = Box::new(TestSource { entries: vec![] }); - (1..2).for_each(|i| { + (1..100).for_each(|i| { ts.add_entry( format! {"Test {} {}", i, s}, format! {"Test {} description", i}, diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index 611b8e9..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::borrow::BorrowMut; -use std::ffi::c_void; - -use log::debug; -use winit::{platform::unix::WindowExtUnix, window::Window}; - -use super::roftl::Entry; - -fn get_visual_type(mut connection: T, window_id: u32) -> xcb::Visualtype -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 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!{"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}) - .expect("Could not match visualid to visualtype"); - - // xcb::Connection calls disconnect on the underlying xcb_connection_t - // when dropped. We cannot let this happen since the xcb_connection_t - // is actually owned by the winit::Window - // We try to signify this with `where T: BorrowMut<*mut c_void>` but - // this is unfortunately not enforced down to the raw pointer - std::mem::forget(xcb_conn); - - visualtype -} - -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 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 - ) - }; - - let connection = unsafe { - cairo::XCBConnection::from_raw_none(winit_xcb_conn as *mut cairo_sys::xcb_connection_t) - }; - - let drawable = cairo::XCBDrawable(window.xlib_window().unwrap() as u32); - - let surface = cairo::XCBSurface::create( - &connection, - &drawable, - &visual_type, - window.inner_size().width as i32, - window.inner_size().height as i32, - ).expect("Could not create drawing surface"); - - cairo::Context::new(&surface).expect("Could not create drawing context") -} - -fn draw_rectangle(cr: &cairo::Context, x: f64, y: f64, width: f64, height: f64, aspect: f64) -{ - let corner_radius = height / 10.0; /* and corner curvature radius */ - let radius = corner_radius / aspect; - let degrees = std::f64::consts::PI / 180.0; - - cr.new_sub_path (); - cr.arc (x + width - radius, y + radius, radius, -90.0 * degrees, 0.0 * degrees); - cr.arc (x + width - radius, y + height - radius, radius, 0.0 * degrees, 90.0 * degrees); - cr.arc (x + radius, y + height - radius, radius, 90.0 * degrees, 180.0 * degrees); - cr.arc (x + radius, y + radius, radius, 180.0 * degrees, 270.0 * degrees); - cr.close_path (); - - cr.set_source_rgb (0.5, 0.5, 1.0); - cr.fill_preserve ().unwrap(); - - cr.set_source_rgb(1.0, 1.0, 1.0); - cr.set_line_width (cr.device_to_user_distance(1.0, 1.0).unwrap().0); - cr.stroke ().unwrap(); -} - -fn draw_text(cr: &cairo::Context, input: &str) -{ - cr.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal); - cr.set_font_size (0.25); - cr.set_source_rgb (0.0, 0.0, 1.0); - cr.move_to (0.05, 0.23); - - if input.len() > 3 { - cr.show_text(&input[..3]).unwrap(); - cr.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Bold); - cr.show_text(&input[3..]).unwrap(); - } else { - cr.show_text(input).unwrap(); - cr.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal); - } -} - -fn draw_result(cr: &cairo::Context, input: &str, x: f64, y: f64) -{ - cr.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal); - cr.set_font_size (0.25); - cr.set_source_rgb (0.0, 0.0, 1.0); - cr.move_to (x, y); - cr.show_text(input).unwrap(); -} - -pub fn draw_on_window(window: &Window, input: &str, result: Vec<&Entry>) -{ - let cr = make_context(&window); - - cr.save().unwrap(); - cr.set_source_rgba(0.0, 0.0, 0.0, 0.5); - cr.set_operator(cairo::Operator::Source); - cr.paint().unwrap(); - cr.restore().unwrap(); - - cr.translate(300.0, 300.0); - cr.scale(100.0, 100.0); - - - draw_rectangle(&cr, 0.0, 0.0, 4.0, 0.3, 2.0); - draw_text(&cr, input); - - result.iter().enumerate() - .for_each(|(i, e)| { - draw_rectangle(&cr, 0.0, 0.3*((1+i) as f64), 4.0, 0.3, 2.0); - draw_result(&cr, &e.name, 0.05, 0.3*((i+1) as f64)+0.23); - }); - - // cr.set_line_width(0.5); - // cr.set_source_rgb(255.0, 255.0, 255.0); - // cr.rectangle(20.0, 20.0, 20.0, 40.0); - // cr.stroke().unwrap(); - - // cr.set_source_rgb(0.0, 0.0, 0.0); - // cr.move_to(0.0, 0.0); - // cr.line_to(1.0, 1.0); - // cr.move_to(1.0, 0.0); - // cr.line_to(0.0, 1.0); - // cr.set_line_width(0.2); - // cr.stroke().unwrap(); - - // cr.rectangle(0.0, 0.0, 0.5, 0.5); - // cr.set_source_rgba(1.0, 0.0, 0.0, 0.80); - // cr.fill().unwrap(); - - // cr.rectangle(0.0, 0.5, 0.5, 0.5); - // cr.set_source_rgba(0.0, 1.0, 0.0, 0.60); - // cr.fill().unwrap(); - - // cr.rectangle(0.5, 0.0, 0.5, 0.5); - // cr.set_source_rgba(0.0, 0.0, 1.0, 0.40); - // cr.fill().unwrap(); -} - diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..c89ba6f --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,4 @@ +mod ui; +pub use ui::draw_on_window; + +mod painter; diff --git a/src/ui/painter.rs b/src/ui/painter.rs new file mode 100644 index 0000000..5c49c25 --- /dev/null +++ b/src/ui/painter.rs @@ -0,0 +1,167 @@ +type Color = (f64, f64, f64, f64); + +struct Theme { + background: Color, + + frame: Color, + frame_width: f64, + + input_bg: Color, + input_font_color: Color, + input_font_size: f64, + + result_bg: Color, + result_bg_selected: Color, + result_font_color: Color, + result_font_selected: Color, + result_font_size: f64, + + divider: Color, + divider_width: f64 +} + +impl Default for Theme { + fn default() -> Self { + let bg_color = (0.5, 0.5, 1.0, 1.0); + Theme { + background: bg_color, + + frame: (1.0, 0.0, 0.0, 1.0), + frame_width: 5.0, + + input_bg: (0.5, 0.5, 1.0, 1.0), + input_font_color: (0.0, 0.0, 1.0, 1.0), + input_font_size: 20.0, + + result_bg: (0.5, 0.5, 1.0, 1.0), + result_bg_selected: (0.5, 0.5, 0.5, 1.0), + result_font_color: (0.0, 0.0, 1.0, 1.0), + result_font_selected: (0.5, 0.5, 0.5, 1.0), + result_font_size: 20.0, + + divider: (0.0, 0.0, 0.0, 1.0), + divider_width: 1.0 + } + } +} + +pub struct Painter<'a> { + context: &'a cairo::Context, + theme: Theme, +} + +impl<'a> Painter<'a> { + pub fn new(context: &'a cairo::Context) -> Self { + Painter{context, theme: Theme::default()} + } + + pub fn background(&self) + { + let ctx = self.context; + + // reset clip so that we can draw the entire background + ctx.reset_clip(); + + // fill the background + let (r,g,b,a) = self.theme.background; + ctx.set_source_rgba(r, g, b, a); + ctx.set_operator(cairo::Operator::Source); + ctx.paint().unwrap(); + + // draw the border + let (x1,y1,x2,y2) = ctx.clip_extents().unwrap(); + let (r,g,b,a) = self.theme.frame; + ctx.rectangle(x1, y1, x2-x1, y2-y1); + ctx.set_source_rgba(r, g, b, a); + let frame_width = self.device_to_user(self.theme.frame_width); + ctx.set_line_width(frame_width); + ctx.stroke().unwrap(); + + ctx.rectangle(x1+(frame_width/2.0), y1+(frame_width/2.0), x2-x1-frame_width, y2-y1-frame_width); + ctx.clip(); + } + + pub fn input_box(&self, x: T, y: T, input: &'a str) + where T: Into + { + let ctx = self.context; + let (x, y): (f64, f64) = (x.into(), y.into()); + + // draw background box + ctx.rectangle(x, y, self.clip_width(), 1.0); + let (r,g,b,a) = self.theme.input_bg; + ctx.set_source_rgba(r,g,b,a); + ctx.fill().unwrap(); + + // draw text + ctx.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal); + let font_size = self.device_to_user(self.theme.input_font_size); + ctx.set_font_size(font_size); + let (r,g,b,a) = self.theme.input_font_color; + ctx.set_source_rgba(r,g,b,a); + ctx.move_to(x + 0.2, y + 0.8); + ctx.show_text(input).unwrap(); + } + + pub fn result_box(&self, x: T, y: T, result: &'a str, selected: bool) + where T: Into + { + 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); + let (r,g,b,a) = if selected { self.theme.result_bg_selected } + else { self.theme.result_bg }; + ctx.set_source_rgba(r,g,b,a); + ctx.fill().unwrap(); + + // draw text + ctx.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal); + let font_size = self.device_to_user(self.theme.result_font_size); + ctx.set_font_size(font_size); + let (r,g,b,a) = if selected { self.theme.result_font_selected } + else { self.theme.result_font_color }; + ctx.set_source_rgba(r,g,b,a); + ctx.move_to (x+0.2, y+0.8); + ctx.show_text(result).unwrap(); + } + + pub fn divider(&self, x: T, y: T) + where T: Into + { + let ctx = self.context; + let (x, y): (f64, f64) = (x.into(), y.into()); + + ctx.move_to(x, y); + + let dash_width = self.device_to_user(self.theme.divider_width); + ctx.set_line_width(dash_width); + ctx.line_to(self.clip_width(), y); + ctx.set_dash(&[self.device_to_user(3.0)], 0.0); + let (r,g,b,a) = self.theme.divider; + ctx.set_source_rgba(r,g,b,a); + ctx.stroke().unwrap(); + + 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 + } + + fn user_to_device(&self, point: f64) -> f64 { + self.context.user_to_device_distance(point, point).unwrap().0 + } + + fn clip_width(&self) -> f64 { + 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(); + y2 - y1 + } +} diff --git a/src/ui/ui.rs b/src/ui/ui.rs new file mode 100644 index 0000000..97ab9d3 --- /dev/null +++ b/src/ui/ui.rs @@ -0,0 +1,91 @@ +use std::borrow::BorrowMut; +use std::ffi::c_void; + +use log::debug; +use winit::{platform::unix::WindowExtUnix, window::Window}; + +use crate::roftl::Entry; + +use super::painter; + +fn get_visual_type(mut connection: T, window_id: u32) -> xcb::Visualtype +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 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!{"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}) + .expect("Could not match visualid to visualtype"); + + // xcb::Connection calls disconnect on the underlying xcb_connection_t + // when dropped. We cannot let this happen since the xcb_connection_t + // is actually owned by the winit::Window + // We try to signify this with `where T: BorrowMut<*mut c_void>` but + // this is unfortunately not enforced down to the raw pointer + std::mem::forget(xcb_conn); + + visualtype +} + +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 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 + ) + }; + + let connection = unsafe { + cairo::XCBConnection::from_raw_none(winit_xcb_conn as *mut cairo_sys::xcb_connection_t) + }; + + let drawable = cairo::XCBDrawable(window.xlib_window().unwrap() as u32); + + let surface = cairo::XCBSurface::create( + &connection, + &drawable, + &visual_type, + window.inner_size().width as i32, + window.inner_size().height as i32, + ).expect("Could not create drawing surface"); + + cairo::Context::new(&surface).expect("Could not create drawing context") +} + +pub fn draw_on_window(window: &Window, input: &str, result: Vec<&Entry>) +{ + 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, e)| { + painter.result_box(0, 1+(i as u32), &e.name, false); + }); +} +