Move to cairo ui
This commit is contained in:
parent
fd54aeece2
commit
2fdfc5b749
4 changed files with 1346 additions and 155 deletions
1187
Cargo.lock
generated
1187
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,10 +6,12 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fltk = "1.1"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
|
|
||||||
|
winit = "0.25"
|
||||||
|
cairo-rs = {version = "0.14.0", features = ["xcb"]}
|
||||||
|
cairo-sys-rs = {version = "0.14.0", features = ["xcb"]}
|
||||||
xcb = "0.9.0"
|
xcb = "0.9.0"
|
||||||
xcb-util = {version = "0.3.0", features = ["ewmh", "icccm"]}
|
xcb-util = {version = "0.3.0", features = ["ewmh", "icccm"]}
|
90
src/main.rs
90
src/main.rs
|
@ -1,6 +1,8 @@
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, trace};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
use winit::{event::{Event, WindowEvent::{CloseRequested, ReceivedCharacter}}, event_loop::{ControlFlow, EventLoop}, window::{Window, WindowBuilder}};
|
||||||
|
|
||||||
mod roftl;
|
mod roftl;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod sources;
|
mod sources;
|
||||||
|
@ -8,37 +10,79 @@ mod sources;
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
debug!{"Set up roftl"};
|
||||||
let roftl = roftl::Roftl::default()
|
let roftl = roftl::Roftl::default()
|
||||||
.add_source(sources::TestSource::new("ts1"))
|
.add_source(sources::TestSource::new("ts1"))
|
||||||
.add_source(sources::Window::new())
|
.add_source(sources::Window::new());
|
||||||
.source();
|
|
||||||
|
|
||||||
let mut app = ui::draw()?;
|
debug!{"Source roftl sources"}
|
||||||
|
roftl.source();
|
||||||
|
|
||||||
roftl
|
let mut input_buffer = String::new();
|
||||||
.narrow("")
|
|
||||||
.iter()
|
|
||||||
.for_each(|e| app.add_candidate((*e).clone()));
|
|
||||||
|
|
||||||
while app.wait() {
|
debug!{"Build window"}
|
||||||
match app.events.recv() {
|
let event_loop = EventLoop::new();
|
||||||
Some(ui::Event::CandidateSelect(i)) => {
|
let window = WindowBuilder::new()
|
||||||
println! {"Selected an entry {}", i}
|
.with_transparent(true)
|
||||||
roftl.action(i, roftl::Action::Primary)
|
.build(&event_loop)
|
||||||
|
.expect("Could not create window");
|
||||||
|
|
||||||
|
debug!{"Draw empty state to window"}
|
||||||
|
ui::draw_on_window(&window, "");
|
||||||
|
|
||||||
|
debug!{"Start event loop"}
|
||||||
|
event_loop.run(move |evt, _win, flow| {
|
||||||
|
*flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
if let Event::WindowEvent{window_id, ..} = evt {
|
||||||
|
if window_id != window.id() {
|
||||||
|
debug!{"Received event for foreign window"}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match evt {
|
||||||
|
Event::WindowEvent{event: CloseRequested, ..} => *flow = ControlFlow::Exit,
|
||||||
|
|
||||||
|
Event::WindowEvent{event: ReceivedCharacter(character), ..} => {
|
||||||
|
*flow = process_input(character, &mut input_buffer, &window)
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ui::Event::InputUpdate) => {
|
Event::RedrawRequested(window_id) if window_id == window.id() => {
|
||||||
trace! {"Input: {}", &app.get_input()}
|
trace!{"Redrawing with input {}", input_buffer}
|
||||||
app.clear_candidates();
|
ui::draw_on_window(&window, &input_buffer);
|
||||||
roftl
|
|
||||||
.narrow(&app.get_input())
|
|
||||||
.iter()
|
|
||||||
.for_each(|e| app.add_candidate((*e).clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => ()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_input(character: char, input_buffer: &mut String, window: &Window) -> ControlFlow {
|
||||||
|
match character {
|
||||||
|
'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"}
|
||||||
|
input_buffer.pop();
|
||||||
|
window.request_redraw();
|
||||||
|
ControlFlow::Wait
|
||||||
|
}
|
||||||
|
|
||||||
|
c if c.is_control() => {
|
||||||
|
debug!{"Got unknown control character"}
|
||||||
|
ControlFlow::Wait
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
trace!{"Push {} to input buffer", character}
|
||||||
|
input_buffer.push(character);
|
||||||
|
window.request_redraw();
|
||||||
|
ControlFlow::Wait
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
220
src/ui.rs
220
src/ui.rs
|
@ -1,124 +1,130 @@
|
||||||
use std::ops::Deref;
|
use std::{borrow::BorrowMut, ffi::c_void};
|
||||||
|
|
||||||
use fltk::app::event_key;
|
use winit::{platform::unix::WindowExtUnix, window::Window};
|
||||||
use fltk::enums::{CallbackTrigger, Font, FrameType, Key};
|
|
||||||
use fltk::group::{PackType, ScrollType};
|
|
||||||
use fltk::prelude::*;
|
|
||||||
use fltk::{app, group, input, window};
|
|
||||||
use fltk::{browser, output};
|
|
||||||
|
|
||||||
use super::roftl::Entry;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
fn get_visual_type<T>(mut connection: T) -> xcb::Visualtype
|
||||||
pub enum Event {
|
where T: BorrowMut<*mut c_void>
|
||||||
InputUpdate,
|
{
|
||||||
CandidateSelect(i32),
|
let xcb_conn = unsafe { xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t) };
|
||||||
|
|
||||||
|
let setup = xcb_conn.get_setup();
|
||||||
|
let mut roots = setup.roots();
|
||||||
|
let screen = roots.next().unwrap();
|
||||||
|
let depth = screen.allowed_depths().next().unwrap();
|
||||||
|
let visualtype = depth.visuals().next().unwrap();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
std::mem::forget(xcb_conn);
|
||||||
|
|
||||||
|
visualtype
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RoftlApp {
|
fn make_context(window: &Window) -> cairo::Context {
|
||||||
pub app: app::App,
|
let winit_xcb_conn = window
|
||||||
pub events: app::Receiver<Event>,
|
.xcb_connection()
|
||||||
|
.expect("Could not get connection from window");
|
||||||
|
|
||||||
send: app::Sender<Event>,
|
let visual_type = unsafe {
|
||||||
input: input::Input,
|
let mut visual_type = get_visual_type(winit_xcb_conn).base;
|
||||||
browser: browser::HoldBrowser,
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoftlApp {
|
fn draw_rectangle(cr: &cairo::Context, x: f64, y: f64, width: f64, height: f64, aspect: f64) {
|
||||||
pub fn add_candidate(&mut self, entry: Entry) {
|
let corner_radius = height / 10.0; /* and corner curvature radius */
|
||||||
let entry_line = format! {"{}\tf{}\t{}", entry.name, entry.description, entry.source};
|
let radius = corner_radius / aspect;
|
||||||
self.browser.add(&entry_line);
|
let degrees = std::f64::consts::PI / 180.0;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_candidates(&mut self) {
|
cr.new_sub_path ();
|
||||||
self.browser.clear();
|
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 ();
|
||||||
|
|
||||||
pub fn get_input(&self) -> String {
|
cr.set_source_rgb (0.5, 0.5, 1.0);
|
||||||
self.input.value().clone()
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for RoftlApp {
|
pub fn draw_on_window(window: &Window, input: &str) {
|
||||||
type Target = app::App;
|
let cr = make_context(&window);
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
cr.translate(300.0, 300.0);
|
||||||
&self.app
|
cr.scale(100.0, 100.0);
|
||||||
|
|
||||||
|
|
||||||
|
draw_rectangle(&cr, 0.0, 0.0, 4.0, 0.3, 2.0);
|
||||||
|
draw_text(&cr, input);
|
||||||
|
|
||||||
|
for i in 1..10 {
|
||||||
|
draw_rectangle(&cr, 0.0, 0.3*(i as f64), 4.0, 0.3, 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw() -> Result<RoftlApp, FltkError> {
|
|
||||||
app::set_font(Font::Screen);
|
|
||||||
|
|
||||||
let app = app::App::default();
|
|
||||||
|
|
||||||
let mut window = window::Window::default()
|
|
||||||
.with_size(400, 300)
|
|
||||||
.center_screen();
|
|
||||||
window.set_border(false);
|
|
||||||
|
|
||||||
let pack = group::Pack::default()
|
|
||||||
.with_type(PackType::Vertical)
|
|
||||||
.with_size(400, 40);
|
|
||||||
|
|
||||||
let mut input = input::Input::default().with_size(0, 40);
|
|
||||||
input.set_trigger(CallbackTrigger::Changed);
|
|
||||||
|
|
||||||
let mut browser: browser::HoldBrowser =
|
|
||||||
browser::HoldBrowser::default().with_size(400, 300 - 40);
|
|
||||||
browser.set_has_scrollbar(browser::BrowserScrollbar::None);
|
|
||||||
|
|
||||||
pack.end();
|
|
||||||
window.end();
|
|
||||||
|
|
||||||
window.show();
|
|
||||||
|
|
||||||
let (s, r) = app::channel::<Event>();
|
|
||||||
|
|
||||||
input.emit(s.clone(), Event::InputUpdate);
|
|
||||||
|
|
||||||
let mut b1 = browser.clone();
|
|
||||||
let s1 = s.clone();
|
|
||||||
input.handle(move |_input, evt| match evt {
|
|
||||||
fltk::enums::Event::KeyUp => {
|
|
||||||
if event_key().eq(&Key::Tab) {
|
|
||||||
b1.take_focus().unwrap();
|
|
||||||
b1.select(1);
|
|
||||||
s1.send(Event::CandidateSelect(b1.value()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut b2 = browser.clone();
|
|
||||||
let s2 = s.clone();
|
|
||||||
browser.handle(move |_input, evt| match evt {
|
|
||||||
fltk::enums::Event::KeyUp => {
|
|
||||||
if event_key().eq(&Key::Tab) {
|
|
||||||
b2.take_focus().unwrap();
|
|
||||||
b2.select(b2.value() + 1);
|
|
||||||
s2.send(Event::CandidateSelect(b2.value()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let s3 = s.clone();
|
|
||||||
browser.set_callback(move |b| {
|
|
||||||
s3.send(Event::CandidateSelect(b.value()));
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(RoftlApp {
|
|
||||||
app,
|
|
||||||
events: r,
|
|
||||||
send: s,
|
|
||||||
input,
|
|
||||||
browser,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue