Initial commit
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
use std::{
|
||||
cmp::min,
|
||||
default::Default,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
draw::{draw_bar, draw_box},
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
const BAR_FONT: &str = "sans";
|
||||
const BAR_SIZE: f32 = 24.;
|
||||
|
||||
pub trait BarWidgetImpl {
|
||||
fn get_dirty(&self) -> bool;
|
||||
fn set_dirty(&mut self, dirty: bool);
|
||||
fn update(&mut self);
|
||||
fn name(&self) -> &'static str;
|
||||
fn value(&self) -> f32;
|
||||
fn color(&self) -> Color;
|
||||
}
|
||||
|
||||
pub struct BarWidget {
|
||||
geometry: Geometry,
|
||||
inner_widget: Box<dyn BarWidgetImpl>,
|
||||
}
|
||||
|
||||
impl BarWidget {
|
||||
pub fn new(inner_widget: Box<dyn BarWidgetImpl>) -> BarWidget {
|
||||
BarWidget {
|
||||
geometry: Default::default(),
|
||||
inner_widget,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for BarWidget {
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.inner_widget.get_dirty()
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.inner_widget.set_dirty(dirty)
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let font = fonts.get_font(BAR_FONT, BAR_SIZE);
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
font.auto_draw_text(view, &bg, &fg, self.inner_widget.name())
|
||||
.unwrap();
|
||||
let size = BAR_SIZE.ceil() as u32;
|
||||
let bar_offset = 4 * size;
|
||||
let val = self.inner_widget.value();
|
||||
|
||||
let c = self.inner_widget.color();
|
||||
draw_bar(
|
||||
&mut view.offset((bar_offset, 0)).unwrap(),
|
||||
&c,
|
||||
self.geometry.width - bar_offset,
|
||||
size,
|
||||
val,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
draw_box(
|
||||
&mut view.offset((bar_offset, 0)).unwrap(),
|
||||
&c,
|
||||
(self.geometry.width - bar_offset, size),
|
||||
)
|
||||
.unwrap();
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
let w = min(geometry.width, 768);
|
||||
let mut x = geometry.x;
|
||||
if geometry.width > w {
|
||||
x += geometry.width - w;
|
||||
}
|
||||
self.geometry = Geometry {
|
||||
x: x,
|
||||
y: geometry.y,
|
||||
width: w,
|
||||
height: BAR_SIZE.ceil() as u32,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
use std::{
|
||||
thread,
|
||||
time::Duration,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use calloop::ping::Ping;
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
widgets::bar_widget::{
|
||||
BarWidget,
|
||||
BarWidgetImpl,
|
||||
},
|
||||
};
|
||||
|
||||
enum UpowerBatteryState {
|
||||
Charging,
|
||||
Discharging,
|
||||
Empty,
|
||||
Full,
|
||||
NotCharging,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
struct InnerBattery {
|
||||
value: f32,
|
||||
state: UpowerBatteryState,
|
||||
}
|
||||
|
||||
fn start_monitor(inner: Arc<Mutex<InnerBattery>>, ping: Ping) {
|
||||
thread::spawn(move || {
|
||||
use dbus::blocking::LocalConnection;
|
||||
use dbus::blocking::stdintf::org_freedesktop_dbus::Properties;
|
||||
use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
|
||||
let conn = LocalConnection::new_system().unwrap();
|
||||
let device_path = "/org/freedesktop/UPower/devices/DisplayDevice";
|
||||
let proxy = conn.with_proxy("org.freedesktop.UPower", device_path, Duration::from_millis(500));
|
||||
|
||||
let capacity: f64 = proxy.get("org.freedesktop.UPower.Device", "Percentage").expect("unable to get property");
|
||||
let state: u32 = proxy.get("org.freedesktop.UPower.Device", "State").expect("unable to get state");
|
||||
|
||||
{
|
||||
let mut inner = inner.lock().unwrap();
|
||||
inner.value = capacity as f32 / 100.;
|
||||
inner.state = match state {
|
||||
1 => UpowerBatteryState::Charging,
|
||||
2 => UpowerBatteryState::Discharging,
|
||||
3 => UpowerBatteryState::Empty,
|
||||
4 => UpowerBatteryState::Full,
|
||||
5 => UpowerBatteryState::NotCharging,
|
||||
6 => UpowerBatteryState::Discharging,
|
||||
_ => UpowerBatteryState::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
proxy.match_signal(move |c: PropertiesPropertiesChanged, _: &LocalConnection, _: &dbus::Message| {
|
||||
if c.interface_name != "org.freedesktop.UPower.Device" {
|
||||
return true;
|
||||
}
|
||||
let mut inner = inner.lock().unwrap();
|
||||
for (key, value) in c.changed_properties {
|
||||
match key.as_str() {
|
||||
"State" => {
|
||||
inner.state = match value.0.as_u64().unwrap() {
|
||||
1 => UpowerBatteryState::Charging,
|
||||
2 => UpowerBatteryState::Discharging,
|
||||
3 => UpowerBatteryState::Empty,
|
||||
4 => UpowerBatteryState::Full,
|
||||
5 => UpowerBatteryState::NotCharging,
|
||||
6 => UpowerBatteryState::Discharging,
|
||||
_ => UpowerBatteryState::Unknown,
|
||||
};
|
||||
ping.ping();
|
||||
},
|
||||
"Percentage" => {
|
||||
inner.value = value.0.as_f64().unwrap() as f32 / 100.;
|
||||
ping.ping();
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
true
|
||||
}).unwrap();
|
||||
|
||||
loop { conn.process(Duration::from_millis(60000)).unwrap(); }
|
||||
});
|
||||
}
|
||||
|
||||
pub struct Battery {
|
||||
inner: Arc<Mutex<InnerBattery>>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Battery {
|
||||
pub fn new(ping: Ping) -> BarWidget {
|
||||
let battery = Battery{
|
||||
inner: Arc::new(Mutex::new(InnerBattery{
|
||||
value: 0.,
|
||||
state: UpowerBatteryState::Unknown,
|
||||
})),
|
||||
dirty: false,
|
||||
};
|
||||
start_monitor(battery.inner.clone(), ping);
|
||||
BarWidget::new(Box::new(battery))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl BarWidgetImpl for Battery {
|
||||
fn update(&mut self) {}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
"battery"
|
||||
}
|
||||
fn value(&self) -> f32 {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.value
|
||||
}
|
||||
fn color(&self) -> Color {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
match inner.state {
|
||||
UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => {
|
||||
if inner.value > 0.1 {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::new(1.0, 0.5, 0.0, 1.0)
|
||||
}
|
||||
}
|
||||
UpowerBatteryState::Charging | UpowerBatteryState::Full => {
|
||||
Color::new(0.5, 1.0, 0.5, 1.0)
|
||||
}
|
||||
UpowerBatteryState::NotCharging | UpowerBatteryState::Empty => {
|
||||
Color::new(1.0, 0.5, 0.5, 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::default::Default;
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
use chrono::{Local, Timelike};
|
||||
|
||||
const CLOCK_FONT: &str = "sans";
|
||||
const CLOCK_SIZE: f32 = 96.;
|
||||
|
||||
pub struct Clock {
|
||||
dirty: bool,
|
||||
geometry: Geometry,
|
||||
digit_width: u32,
|
||||
colon_width: u32,
|
||||
}
|
||||
|
||||
impl<'a> Clock {
|
||||
pub fn new() -> Clock {
|
||||
Clock {
|
||||
dirty: true,
|
||||
geometry: Default::default(),
|
||||
digit_width: 0,
|
||||
colon_width: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Clock {
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty;
|
||||
}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let time = Local::now().naive_local();
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let digit = self.digit_width;
|
||||
let colon = self.colon_width;
|
||||
let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE);
|
||||
font.draw_text_fixed_width(
|
||||
view,
|
||||
&bg,
|
||||
&fg,
|
||||
&[digit, digit, colon, digit, digit],
|
||||
&format!("{:02}:{:02}", time.hour(), time.minute()),
|
||||
)
|
||||
.unwrap();
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE);
|
||||
self.digit_width = font.auto_widest("0123456789").unwrap();
|
||||
self.colon_width = font.auto_widest(":").unwrap();
|
||||
self.geometry = Geometry {
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
width: self.digit_width * 4 + self.colon_width,
|
||||
height: CLOCK_SIZE.ceil() as u32,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::default::Default;
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
use chrono::{Datelike, Local};
|
||||
|
||||
const DATE_FONT: &str = "sans";
|
||||
const DATE_SIZE: f32 = 40.;
|
||||
|
||||
pub struct Date {
|
||||
dirty: bool,
|
||||
geometry: Geometry,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
pub fn new() -> Date {
|
||||
Date {
|
||||
dirty: true,
|
||||
geometry: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Date {
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty;
|
||||
}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let time = Local::now().naive_local();
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
let mut date_line = view.offset((0, 8)).unwrap();
|
||||
let font = fonts.get_font(DATE_FONT, DATE_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut date_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(
|
||||
"{}, {:02}/{:02}/{:4}",
|
||||
time.weekday(),
|
||||
time.day(),
|
||||
time.month(),
|
||||
time.year()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
self.geometry = Geometry {
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
width: 256,
|
||||
height: DATE_SIZE.ceil() as u32,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
use std::cmp::{min, Ordering};
|
||||
use std::collections::HashMap;
|
||||
use std::default::Default;
|
||||
use std::process::{exit, Command};
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
keyboard::{keysyms, KeyEvent},
|
||||
utils::desktop::{load_desktop_files, Desktop},
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wayland_client::{protocol::wl_keyboard, WEnum};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
const LAUNCHER_FONT: &str = "monospace";
|
||||
const LAUNCHER_SIZE: f32 = 32.;
|
||||
|
||||
struct Matcher {
|
||||
matches: HashMap<Desktop, i64>,
|
||||
}
|
||||
|
||||
impl Matcher {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
matches: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_match(&mut self, dtop: Desktop, val: &str, input: &str, prio: f32) {
|
||||
let fuzzy_matcher = SkimMatcherV2::default();
|
||||
if let Some(ma) = fuzzy_matcher.fuzzy_match(val, input) {
|
||||
let ma = ((ma as f32) * prio) as i64;
|
||||
if let Some(ma_old) = self.matches.get(&dtop) {
|
||||
// Skip over new matches for the same program that are worse
|
||||
// than the one we already have.
|
||||
if ma_old > &ma {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.matches.insert(dtop, ma);
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(&self) -> Vec<Desktop> {
|
||||
let mut m = self
|
||||
.matches
|
||||
.iter()
|
||||
.map(|(key, ma)| (*ma, key.clone()))
|
||||
.collect::<Vec<(i64, Desktop)>>();
|
||||
|
||||
m.sort_by(|(ma1, d1), (ma2, d2)| {
|
||||
if ma1 > ma2 {
|
||||
Ordering::Less
|
||||
} else if ma1 < ma2 {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
d1.cmp(d2)
|
||||
}
|
||||
});
|
||||
|
||||
m.into_iter().map(|(_, x)| x).collect()
|
||||
}
|
||||
}
|
||||
|
||||
enum PromptMode {
|
||||
Normal,
|
||||
Shell,
|
||||
Calc,
|
||||
}
|
||||
|
||||
struct Prompt {
|
||||
input: String,
|
||||
mode: PromptMode,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
fn new() -> Prompt {
|
||||
Prompt {
|
||||
input: String::new(),
|
||||
mode: PromptMode::Normal,
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn move_cursor(&mut self, distance: isize) {
|
||||
let new_cursor = self.cursor as isize + distance;
|
||||
if new_cursor < 0 {
|
||||
self.cursor = 0;
|
||||
return;
|
||||
} else if new_cursor > self.input.len() as isize {
|
||||
self.cursor = self.input.len()
|
||||
} else {
|
||||
self.cursor = new_cursor as usize;
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, v: &str) {
|
||||
if self.input.len() == 0 {
|
||||
match v {
|
||||
"!" => {
|
||||
self.mode = PromptMode::Shell;
|
||||
return;
|
||||
},
|
||||
"=" => {
|
||||
self.mode = PromptMode::Calc;
|
||||
return;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect();
|
||||
if self.cursor == indices.len() {
|
||||
self.input += &v;
|
||||
} else {
|
||||
let index_at = indices[self.cursor].0;
|
||||
self.input.insert_str(index_at, &v);
|
||||
}
|
||||
self.cursor += 1;
|
||||
}
|
||||
|
||||
fn backspace(&mut self) {
|
||||
let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect();
|
||||
if !indices.is_empty() && self.cursor > 0 {
|
||||
self.cursor -= 1;
|
||||
indices.remove(self.cursor);
|
||||
self.input = indices.iter().fold("".into(), |acc, el| acc + el.1);
|
||||
} else {
|
||||
self.mode = PromptMode::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self) {
|
||||
let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect();
|
||||
if !indices.is_empty() && self.cursor < indices.len() {
|
||||
indices.remove(self.cursor);
|
||||
self.input = indices.iter().fold("".into(), |acc, el| acc + el.1);
|
||||
} else {
|
||||
self.mode = PromptMode::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
fn home(&mut self) {
|
||||
self.cursor = 0;
|
||||
}
|
||||
|
||||
fn end(&mut self) {
|
||||
self.cursor = self.input.len();
|
||||
}
|
||||
}
|
||||
|
||||
trait InterfaceWidget {
|
||||
fn trigger(&mut self, intf: &InnerInterface);
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry;
|
||||
fn update(&mut self, intf: &InnerInterface);
|
||||
}
|
||||
|
||||
struct Launcher {
|
||||
next_token: Option<String>,
|
||||
options: Arc<Mutex<Vec<Desktop>>>,
|
||||
matches: Vec<Desktop>,
|
||||
}
|
||||
|
||||
impl Launcher {
|
||||
fn new() -> Launcher {
|
||||
let options = Arc::new(Mutex::new(Vec::new()));
|
||||
{
|
||||
let options = Arc::clone(&options);
|
||||
thread::spawn(move || {
|
||||
let mut options = options.lock().unwrap();
|
||||
*options = load_desktop_files();
|
||||
});
|
||||
}
|
||||
|
||||
Launcher {
|
||||
next_token: None,
|
||||
options: options,
|
||||
matches: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn exec(&self, args: Vec<String>) {
|
||||
let mut cmd = Command::new(args[0].clone());
|
||||
if let Some(token) = &self.next_token {
|
||||
cmd.env("XDG_ACTIVATION_TOKEN", token);
|
||||
}
|
||||
cmd.args(&args[1..]).spawn().unwrap();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceWidget for Launcher {
|
||||
fn trigger(&mut self, intf: &InnerInterface) {
|
||||
if self.matches.len() > intf.selection {
|
||||
let d = &self.matches[intf.selection];
|
||||
if let Some(exec) = &d.exec {
|
||||
let exec = exec
|
||||
.replace("%f", "")
|
||||
.replace("%F", "")
|
||||
.replace("%u", "")
|
||||
.replace("%U", "");
|
||||
|
||||
let lexed = shlex::split(&exec).unwrap();
|
||||
if !lexed.is_empty() {
|
||||
self.exec(lexed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, intf: &InnerInterface) {
|
||||
let mut matcher = Matcher::new();
|
||||
let options = self.options.lock().unwrap();
|
||||
for desktop in options.iter() {
|
||||
matcher.try_match(
|
||||
desktop.clone(),
|
||||
&desktop.name.to_lowercase(),
|
||||
&intf.prompt.input.to_lowercase(),
|
||||
1.0,
|
||||
);
|
||||
for keyword in desktop.keywords.iter() {
|
||||
matcher.try_match(
|
||||
desktop.clone(),
|
||||
&keyword.to_lowercase(),
|
||||
&intf.prompt.input.to_lowercase(),
|
||||
0.5,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.matches = matcher.matches();
|
||||
}
|
||||
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let line_height = LAUNCHER_SIZE.ceil() as u32;
|
||||
|
||||
// Draw line
|
||||
let mut prompt_offset = intf.geometry.height - line_height;
|
||||
let mut prompt_line = view.offset((0, prompt_offset)).unwrap();
|
||||
let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap();
|
||||
prompt_border.memset(&fg);
|
||||
|
||||
// Draw prompt
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text_with_cursor(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(" {}", &intf.prompt.input),
|
||||
intf.prompt.cursor + 3,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
">",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Draw entries
|
||||
let dimfg = Color::new(0.5, 0.5, 0.5, 1.0);
|
||||
prompt_offset -= 16;
|
||||
|
||||
for (idx, m) in self.matches.iter().enumerate() {
|
||||
if prompt_offset < line_height {
|
||||
break;
|
||||
}
|
||||
prompt_offset -= line_height;
|
||||
let mut line = view.offset((0, prompt_offset)).unwrap();
|
||||
|
||||
if idx == intf.selection {
|
||||
let fuzzy_matcher = SkimMatcherV2::default();
|
||||
let (_, indices) = fuzzy_matcher
|
||||
.fuzzy_indices(&m.name.to_lowercase(), &intf.prompt.input.to_lowercase())
|
||||
.unwrap_or((0, vec![]));
|
||||
|
||||
let mut colors = Vec::with_capacity(m.name.len());
|
||||
for pos in 0..m.name.len() {
|
||||
if indices.contains(&pos) {
|
||||
colors.push(Color::new(1.0, 0.65, 0., 1.0));
|
||||
} else {
|
||||
colors.push(Color::new(0.75, 0.75, 0.75, 1.0));
|
||||
}
|
||||
}
|
||||
font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name)
|
||||
.unwrap();
|
||||
} else {
|
||||
font.auto_draw_text(&mut line, &bg, &dimfg, &m.name)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let content_height = min(intf.geometry.height, (self.matches.len() + 1) as u32 * line_height + 16);
|
||||
Geometry{
|
||||
x: intf.geometry.x,
|
||||
y: intf.geometry.y + intf.geometry.height - content_height,
|
||||
width: intf.geometry.width,
|
||||
height: content_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Shell ();
|
||||
|
||||
impl InterfaceWidget for Shell {
|
||||
fn trigger(&mut self, intf: &InnerInterface) {
|
||||
let args = vec![
|
||||
"/bin/sh".to_string(),
|
||||
"-c".to_string(),
|
||||
intf.prompt.input.clone(),
|
||||
];
|
||||
Command::new(args[0].clone())
|
||||
.args(&args[1..])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
fn update(&mut self, _intf: &InnerInterface) {}
|
||||
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let line_height = LAUNCHER_SIZE.ceil() as u32;
|
||||
|
||||
// Draw line
|
||||
let prompt_offset = intf.geometry.height - line_height;
|
||||
let mut prompt_line = view.offset((0, prompt_offset)).unwrap();
|
||||
let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap();
|
||||
prompt_border.memset(&fg);
|
||||
|
||||
// Draw prompt
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text_with_cursor(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(" {}", &intf.prompt.input),
|
||||
intf.prompt.cursor + 3,
|
||||
)
|
||||
.unwrap();
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&Color::new(1., 0.75, 0.5, 1.),
|
||||
"!",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Geometry{
|
||||
x: intf.geometry.x,
|
||||
y: intf.geometry.y + intf.geometry.height - line_height,
|
||||
width: intf.geometry.width,
|
||||
height: line_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Calc {
|
||||
old: Vec<String>,
|
||||
result: Option<String>,
|
||||
}
|
||||
|
||||
impl InterfaceWidget for Calc {
|
||||
fn trigger(&mut self, intf: &InnerInterface) {
|
||||
if let Some(res) = &self.result {
|
||||
self.old.push(format!("{} = {}", &intf.prompt.input, res));
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, intf: &InnerInterface) {
|
||||
let res = rcalc_lib::parse::eval(&intf.prompt.input, &mut rcalc_lib::parse::CalcState::new())
|
||||
.map(|x| format!("{}", x))
|
||||
.map_err(|x| format!("{}", x));
|
||||
self.result = match res {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
};
|
||||
}
|
||||
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let line_height = LAUNCHER_SIZE.ceil() as u32;
|
||||
|
||||
// Draw line
|
||||
let mut prompt_offset = intf.geometry.height - (LAUNCHER_SIZE.ceil() as u32);
|
||||
let mut prompt_line = view.offset((0, prompt_offset)).unwrap();
|
||||
let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap();
|
||||
prompt_border.memset(&fg);
|
||||
|
||||
// Draw prompt
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
let res_off = font.auto_draw_text_with_cursor(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(" {} ", &intf.prompt.input),
|
||||
intf.prompt.cursor + 3,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&Color::new(1., 0.75, 0.5, 1.),
|
||||
"=",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(res) = &self.result {
|
||||
let resfg = Color::new(1.0, 0.65, 0., 1.0);
|
||||
let mut result_line = view.offset((res_off.0, prompt_offset)).unwrap();
|
||||
font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)).unwrap();
|
||||
}
|
||||
|
||||
for m in self.old.iter().rev() {
|
||||
let dimfg = Color::new(0.5, 0.5, 0.5, 1.0);
|
||||
if prompt_offset < LAUNCHER_SIZE.ceil() as u32 {
|
||||
break;
|
||||
}
|
||||
prompt_offset -= LAUNCHER_SIZE.ceil() as u32;
|
||||
let mut result_line = view.offset((0, prompt_offset)).unwrap();
|
||||
font.auto_draw_text(&mut result_line, &bg, &dimfg, &m).unwrap();
|
||||
}
|
||||
|
||||
let content_height = min(intf.geometry.height, (self.old.len() + 1) as u32 * line_height + 16);
|
||||
Geometry{
|
||||
x: intf.geometry.x,
|
||||
y: intf.geometry.y + intf.geometry.height - content_height,
|
||||
width: intf.geometry.width,
|
||||
height: content_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerInterface {
|
||||
dirty: bool,
|
||||
geometry: Geometry,
|
||||
selection: usize,
|
||||
prompt: Prompt,
|
||||
}
|
||||
|
||||
pub struct Interface {
|
||||
launcher: Launcher,
|
||||
shell: Shell,
|
||||
calc: Calc,
|
||||
inner: InnerInterface,
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
pub fn new() -> Interface {
|
||||
Interface{
|
||||
launcher: Launcher::new(),
|
||||
shell: Shell{},
|
||||
calc: Calc{
|
||||
old: Vec::new(),
|
||||
result: None,
|
||||
},
|
||||
inner: InnerInterface{
|
||||
dirty: false,
|
||||
geometry: Default::default(),
|
||||
selection: 0,
|
||||
prompt: Prompt::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Interface {
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.inner.dirty = dirty;
|
||||
}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.inner.dirty
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.inner.geometry
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
match self.inner.prompt.mode {
|
||||
PromptMode::Shell => self.shell.draw(&self.inner, fonts, view),
|
||||
PromptMode::Normal => self.launcher.draw(&self.inner, fonts, view),
|
||||
PromptMode::Calc => self.calc.draw(&self.inner, fonts, view),
|
||||
}
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
self.inner.geometry = geometry.clone();
|
||||
self.inner.geometry
|
||||
}
|
||||
|
||||
fn keyboard_input(&mut self, event: &KeyEvent) {
|
||||
if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) {
|
||||
return;
|
||||
}
|
||||
self.inner.dirty = true;
|
||||
let widget = match self.inner.prompt.mode {
|
||||
PromptMode::Shell => &mut self.shell as &mut dyn InterfaceWidget,
|
||||
PromptMode::Normal => &mut self.launcher as &mut dyn InterfaceWidget,
|
||||
PromptMode::Calc => &mut self.calc as &mut dyn InterfaceWidget,
|
||||
};
|
||||
match event.keysym {
|
||||
keysyms::XKB_KEY_a if event.modifiers.ctrl => self.inner.prompt.home(),
|
||||
keysyms::XKB_KEY_e if event.modifiers.ctrl => self.inner.prompt.end(),
|
||||
keysyms::XKB_KEY_Home => self.inner.prompt.home(),
|
||||
keysyms::XKB_KEY_End => self.inner.prompt.end(),
|
||||
keysyms::XKB_KEY_BackSpace => {
|
||||
self.inner.prompt.backspace();
|
||||
self.inner.selection = 0;
|
||||
widget.update(&self.inner);
|
||||
}
|
||||
keysyms::XKB_KEY_Delete => {
|
||||
self.inner.prompt.delete();
|
||||
self.inner.selection = 0;
|
||||
widget.update(&self.inner);
|
||||
}
|
||||
keysyms::XKB_KEY_Return => widget.trigger(&self.inner),
|
||||
keysyms::XKB_KEY_Left => self.inner.prompt.move_cursor(-1),
|
||||
keysyms::XKB_KEY_Right => self.inner.prompt.move_cursor(1),
|
||||
keysyms::XKB_KEY_Up => {
|
||||
self.inner.selection += 1;
|
||||
}
|
||||
keysyms::XKB_KEY_Down => {
|
||||
if self.inner.selection > 0 {
|
||||
self.inner.selection -= 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(utf8) = &event.utf8 {
|
||||
self.inner.prompt.append(&utf8);
|
||||
self.inner.selection = 0;
|
||||
widget.update(&self.inner);
|
||||
} else {
|
||||
self.inner.dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn token_update(&mut self, token: &str) {
|
||||
self.launcher.next_token = Some(token.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use std::default::Default;
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Line {
|
||||
geometry: Geometry,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new() -> Line {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Line {
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
view.memset(&Color::new(1., 1., 1., 1.));
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
self.geometry = Geometry {
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
width: geometry.width,
|
||||
height: 1,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
mod bar_widget;
|
||||
mod battery;
|
||||
mod clock;
|
||||
mod date;
|
||||
mod launcher;
|
||||
mod line;
|
||||
mod widget;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use clock::Clock;
|
||||
pub use date::Date;
|
||||
pub use launcher::Interface;
|
||||
pub use line::Line;
|
||||
pub use widget::{
|
||||
Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget,
|
||||
WidgetUpdater,
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
use std::cmp::{min, max};
|
||||
use std::fmt;
|
||||
|
||||
use crate::{buffer::BufferView, fonts::FontMap, keyboard::KeyEvent};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Geometry {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for Geometry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"(x: {}, y: {}, width: {}, height: {})",
|
||||
self.x, self.y, self.width, self.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Geometry {
|
||||
pub fn new() -> Geometry {
|
||||
Geometry{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand(&self, other: Geometry) -> Geometry {
|
||||
let x = min(self.x, other.x);
|
||||
let y = min(self.y, other.y);
|
||||
Geometry {
|
||||
x, y,
|
||||
width: max(self.width + self.x, other.width + other.x) - x,
|
||||
height: max(self.height + self.y, other.height + other.height) - y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Widget {
|
||||
fn get_dirty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn set_dirty(&mut self, _dirty: bool) {}
|
||||
fn geometry(&self) -> Geometry;
|
||||
fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry;
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry;
|
||||
fn keyboard_input(&mut self, _event: &KeyEvent) {}
|
||||
fn token_update(&mut self, _token: &str) {}
|
||||
}
|
||||
|
||||
pub trait Layout<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry;
|
||||
}
|
||||
pub struct HorizontalLayout<U> {
|
||||
pub widgets: Vec<Box<dyn Layout<U>>>,
|
||||
}
|
||||
|
||||
impl<U> Layout<U> for HorizontalLayout<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
let mut geo = geometry.clone();
|
||||
let mut max_width = 0;
|
||||
let mut max_height = 0;
|
||||
for w in self.widgets.iter_mut() {
|
||||
let result = w.geometry_update(fonts, &geo, user_data);
|
||||
geo.x = result.x + result.width;
|
||||
geo.width -= result.width;
|
||||
max_width += result.width;
|
||||
max_height = max(result.height, max_height);
|
||||
}
|
||||
geo.width = max_width;
|
||||
geo.height = max_height;
|
||||
geo
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VerticalLayout<U> {
|
||||
pub widgets: Vec<Box<dyn Layout<U>>>,
|
||||
}
|
||||
|
||||
impl<U> Layout<U> for VerticalLayout<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
let mut geo = geometry.clone();
|
||||
let mut max_width = 0;
|
||||
let mut max_height = 0;
|
||||
for w in self.widgets.iter_mut() {
|
||||
let result = w.geometry_update(fonts, &geo, user_data);
|
||||
geo.y = result.y + result.height;
|
||||
geo.height -= result.height;
|
||||
max_width = max(result.width, max_width);
|
||||
max_height += result.height;
|
||||
}
|
||||
geo.width = max_width;
|
||||
geo.height = max_height;
|
||||
geo
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetUpdater {
|
||||
fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry)
|
||||
-> Geometry;
|
||||
}
|
||||
|
||||
pub struct IndexedLayout {
|
||||
pub widget_idx: usize,
|
||||
}
|
||||
|
||||
impl<U: WidgetUpdater> Layout<U> for IndexedLayout {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
user_data.geometry_update(self.widget_idx, fonts, geometry)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Margin<U> {
|
||||
pub widget: Box<dyn Layout<U>>,
|
||||
pub margin: (u32, u32, u32, u32),
|
||||
}
|
||||
|
||||
impl<U> Layout<U> for Margin<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
let geo = Geometry{
|
||||
x: geometry.x + self.margin.0,
|
||||
y: geometry.y + self.margin.1,
|
||||
width: geometry.width - self.margin.0 - self.margin.2,
|
||||
height: geometry.height - self.margin.1 - self.margin.3,
|
||||
};
|
||||
|
||||
let out = self.widget.geometry_update(fonts, &geo, user_data);
|
||||
Geometry {
|
||||
x: out.x - self.margin.0,
|
||||
y: out.y - self.margin.1,
|
||||
width: out.width + self.margin.0 + self.margin.2,
|
||||
height: out.height + self.margin.1 + self.margin.3,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user