More stuff

This commit is contained in:
Kenny Levinsen
2023-02-02 23:52:59 +01:00
parent 7593cd48a4
commit b49b9e9764
26 changed files with 2022 additions and 803 deletions
+238
View File
@@ -0,0 +1,238 @@
use std::{
sync::{Arc, Mutex},
thread,
error::Error,
rc::Rc,
cell::RefCell,
os::fd::RawFd,
};
use nix::unistd::{
pipe, write, read,
};
use crate::{
color::Color,
event::{Event, Events, PointerButton},
fonts::FontMap,
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
use libpulse_binding::volume::ChannelVolumes;
fn monitor(events: Arc<Mutex<Events>>, inner: Arc<Mutex<InnerAudio>>, pipe: RawFd) -> Result<(), Box<dyn Error>> {
use libpulse_binding::{
mainloop::{
self,
standard::{
IterateResult,
},
api::Mainloop,
events::io::FlagSet as IoFlagSet,
},
callbacks::ListResult,
context::{
Context,
FlagSet,
subscribe::{InterestMaskSet, Facility},
State,
},
proplist::{properties, Proplist},
volume::Volume,
};
let mut proplist = Proplist::new().unwrap();
proplist
.set_str(properties::APPLICATION_NAME, "wldash")
.unwrap();
let mut mainloop = mainloop::standard::Mainloop::new().unwrap();
let context = Rc::new(RefCell::new(Context::new_with_proplist(&mainloop, "wldash_context", &proplist).unwrap()));
context.borrow_mut().connect(None, FlagSet::NOFLAGS, None)?;
loop {
mainloop.iterate(false);
match context.borrow().get_state() {
State::Ready => break,
State::Failed | State::Terminated => panic!("damn"),
_ => (),
}
}
let local_context = context.clone();
let local_inner = inner.clone();
let local_events = events.clone();
context
.borrow_mut()
.set_subscribe_callback(Some(Box::new(move |facility, _operation, index| {
match facility {
Some(Facility::Sink) => {
let introspector = local_context.borrow_mut().introspect();
let inner = local_inner.clone();
let events = local_events.clone();
introspector.get_sink_info_by_index(index, move |res| {
match res {
ListResult::End | ListResult::Error => (),
ListResult::Item(sink_info) => if let Some(_) = &sink_info.name {
let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
let mut inner = inner.lock().unwrap();
inner.volume = avg;
inner.mute = sink_info.mute;
inner.dirty = true;
drop(inner);
let mut events = events.lock().unwrap();
events.add_event(Event::AudioUpdate);
},
}
});
},
_ => (),
}
})));
context
.borrow_mut()
.subscribe(InterestMaskSet::SINK, |_| {});
let introspector = context.borrow_mut().introspect();
let local_inner = inner.clone();
introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |res| {
match res {
ListResult::End | ListResult::Error => (),
ListResult::Item(sink_info) => if let Some(_) = &sink_info.name {
let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
let mut inner = local_inner.lock().unwrap();
inner.volume = avg;
inner.channel_volume = Some(sink_info.volume);
inner.mute = sink_info.mute;
inner.dirty = true;
drop(inner);
let mut events = events.lock().unwrap();
events.add_event(Event::AudioUpdate);
},
}
});
drop(introspector);
let local_context = context.clone();
let local_inner = inner.clone();
let event_source = mainloop.new_io_event(pipe as i32, IoFlagSet::INPUT, Box::new(move |_, _, _| {
let mut buf = [0; 8];
_ = read(pipe, &mut buf);
let mut inner = local_inner.lock().unwrap();
let mut introspector = local_context.borrow_mut().introspect();
match inner.last_click.take() {
Some(Change::Volume(volume)) => {
// do something
let mut vol = inner.channel_volume.unwrap();
vol.scale(Volume((volume * Volume::NORMAL.0 as f32).round() as u32));
introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None);
},
Some(Change::VolumeInc(value)) => {
let mut vol = inner.channel_volume.unwrap();
// apply step to volumes
let step = (value * Volume::NORMAL.0 as f32).round() as i32;
if step > 0 {
vol.inc_clamp(Volume(step as u32), Volume::NORMAL);
} else {
vol.decrease(Volume(-step as u32));
}
// HACK: It would be better to only update the volume from feedback, but axis
// events fire fast and require the last set volume immediately for reference.
inner.channel_volume = Some(vol);
introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None);
},
Some(Change::ToggleMute) => {
introspector.set_sink_mute_by_name("@DEFAULT_SINK@", !inner.mute, None);
},
None => (),
}
}));
loop {
match mainloop.iterate(true) {
IterateResult::Quit(_) | IterateResult::Err(_) => break,
_ => (),
}
}
// Need to keep the event source alive
_ = event_source;
Ok(())
}
fn start_monitor(events: Arc<Mutex<Events>>, inner: Arc<Mutex<InnerAudio>>, pipe: RawFd) {
thread::Builder::new()
.name("audiomon".to_string())
.spawn(move || { monitor(events, inner, pipe).unwrap() })
.unwrap();
}
enum Change {
Volume(f32),
VolumeInc(f32),
ToggleMute,
}
pub struct InnerAudio {
volume: f32,
channel_volume: Option<ChannelVolumes>,
mute: bool,
dirty: bool,
last_click: Option<Change>,
pipe: RawFd,
}
pub struct Audio {
inner: Arc<Mutex<InnerAudio>>,
}
impl Audio {
pub fn new(events: Arc<Mutex<Events>>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
let (a, b) = pipe().unwrap();
let audio = Audio {
inner: Arc::new(Mutex::new(InnerAudio{
volume: 0.0,
channel_volume: None,
mute: false,
dirty: false,
last_click: None,
pipe: b,
})),
};
start_monitor(events, audio.inner.clone(), a);
BarWidget::new(Box::new(audio), fm, font, size)
}
}
impl BarWidgetImpl for Audio {
fn get_dirty(&self) -> bool {
self.inner.lock().unwrap().dirty
}
fn name(&self) -> &'static str {
"volume"
}
fn value(&self) -> f32 {
self.inner.lock().unwrap().volume
}
fn color(&self) -> Color {
match self.inner.lock().unwrap().mute {
true => Color::YELLOW,
false => Color::WHITE,
}
}
fn click(&mut self, pos: f32, btn: PointerButton) {
let mut inner = self.inner.lock().unwrap();
inner.last_click = match btn {
PointerButton::Left => Some(Change::Volume(pos)),
PointerButton::Right => Some(Change::ToggleMute),
PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)),
PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)),
_ => None,
};
_ = write(inner.pipe, &[0]);
}
}
+4 -3
View File
@@ -6,6 +6,7 @@ use std::{
use crate::{
color::Color,
fonts::FontMap,
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
@@ -28,7 +29,7 @@ impl Backlight {
self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap();
}
pub fn new(path: &str, font: &'static str, size: f32) -> BarWidget {
pub fn new(path: &str, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
let mut dev = Backlight {
device_path: Path::new("/sys/class/backlight").to_path_buf().join(path),
cur: 0,
@@ -36,7 +37,7 @@ impl Backlight {
};
dev.update();
BarWidget::new(Box::new(dev), font, size)
BarWidget::new(Box::new(dev), fm, font, size)
}
}
@@ -53,6 +54,6 @@ impl BarWidgetImpl for Backlight {
self.cur as f32 / self.max as f32
}
fn color(&self) -> Color {
Color::new(1.0, 1.0, 1.0, 1.0)
Color::WHITE
}
}
+25 -8
View File
@@ -6,6 +6,7 @@ use crate::{
draw::{draw_bar, draw_box},
fonts::FontMap,
widgets::{Geometry, Widget},
event::{Event, PointerButton},
};
pub trait BarWidgetImpl {
@@ -15,6 +16,7 @@ pub trait BarWidgetImpl {
fn name(&self) -> &'static str;
fn value(&self) -> f32;
fn color(&self) -> Color;
fn click(&mut self, _pos: f32, _btn: PointerButton) {}
}
pub struct BarWidget {
@@ -25,7 +27,8 @@ pub struct BarWidget {
}
impl BarWidget {
pub fn new(inner_widget: Box<dyn BarWidgetImpl>, font: &'static str, size: f32) -> BarWidget {
pub fn new(inner_widget: Box<dyn BarWidgetImpl>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
fm.queue_font(font, size, inner_widget.name());
BarWidget {
geometry: Default::default(),
inner_widget,
@@ -46,10 +49,10 @@ impl Widget for BarWidget {
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
let font = fonts.get_font(self.font, self.size);
let fg = Color::new(1., 1., 1., 1.);
let bg = Color::new(0., 0., 0., 1.);
let fg = Color::WHITE;
let bg = Color::BLACK;
font.auto_draw_text(view, &bg, &fg, self.inner_widget.name())
font.draw_text(view, bg, fg, self.inner_widget.name())
.unwrap();
let size = self.size.ceil() as u32;
let bar_offset = 4 * size;
@@ -57,8 +60,8 @@ impl Widget for BarWidget {
let c = self.inner_widget.color();
draw_bar(
&mut view.offset((bar_offset, 0)).unwrap(),
&c,
&mut view.offset((bar_offset, 0)),
c,
self.geometry.width - bar_offset,
size,
val,
@@ -66,8 +69,8 @@ impl Widget for BarWidget {
.unwrap();
draw_box(
&mut view.offset((bar_offset, 0)).unwrap(),
&c,
&mut view.offset((bar_offset, 0)),
c,
(self.geometry.width - bar_offset, size),
)
.unwrap();
@@ -88,4 +91,18 @@ impl Widget for BarWidget {
};
self.geometry
}
fn event(&mut self, event: &Event) {
match event {
Event::PointerEvent(ev) => {
let offset = 4 * self.size.ceil() as u32;
if ev.pos.0 >= offset {
let val = (ev.pos.0 - offset) as f32 / (self.geometry.width - offset) as f32;
self.inner_widget.click(val, ev.button);
}
},
_ => (),
}
}
}
+68 -103
View File
@@ -1,110 +1,81 @@
use std::{
sync::{Arc, Mutex},
thread,
time::Duration,
};
use dbus::blocking::{
stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged},
LocalConnection,
error::Error,
};
use crate::{
color::Color,
state::{Event, Events},
event::{Event, Events},
fonts::FontMap,
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
enum UpowerBatteryState {
Charging,
Discharging,
Empty,
Full,
NotCharging,
Unknown,
}
use upower_dbus::BatteryState;
struct InnerBattery {
value: f32,
state: UpowerBatteryState,
state: BatteryState,
dirty: bool,
}
async fn monitor(inner: Arc<Mutex<InnerBattery>>, events: Arc<Mutex<Events>>) -> Result<(), Box<dyn Error>> {
use zbus::Connection;
use upower_dbus::UPowerProxy;
use tokio_stream::StreamExt;
let conn = Connection::system().await?;
let proxy = UPowerProxy::new(&conn).await?;
let device = proxy.get_display_device().await?;
let local_inner = inner.clone();
let local_events = events.clone();
let mut percent_stream = device.receive_percentage_changed().await;
let a = tokio::spawn(async move {
while let Some(percent) = percent_stream.next().await {
let percent = percent.get().await.unwrap() as f32 / 100.;
let mut inner = local_inner.lock().unwrap();
inner.value = percent;
inner.dirty = true;
drop(inner);
let mut events = local_events.lock().unwrap();
events.add_event(Event::PowerUpdate);
}
});
let local_inner = inner.clone();
let local_events = events.clone();
let mut state_stream = device.receive_state_changed().await;
let b = tokio::spawn(async move {
while let Some(state) = state_stream.next().await {
let state = state.get().await.unwrap();
let mut inner = local_inner.lock().unwrap();
inner.state = state;
inner.dirty = true;
drop(inner);
let mut events = local_events.lock().unwrap();
events.add_event(Event::PowerUpdate);
}
});
a.await?;
b.await?;
Ok(())
}
fn start_monitor(inner: Arc<Mutex<InnerBattery>>, events: Arc<Mutex<Events>>) {
use tokio::runtime;
thread::Builder::new()
.name("battmon".to_string())
.spawn(move || {
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,
};
inner.dirty = true;
let mut events = events.lock().unwrap();
events.add_event(Event::PowerUpdate);
}
"Percentage" => {
inner.value = value.0.as_f64().unwrap() as f32 / 100.;
inner.dirty = true;
let mut events = events.lock().unwrap();
events.add_event(Event::PowerUpdate);
}
_ => (),
}
}
true
},
)
let rt = runtime::Builder::new_current_thread()
.enable_io()
.build()
.unwrap();
loop {
conn.process(Duration::from_millis(60000)).unwrap();
}
rt.block_on(async move { monitor(inner, events).await }).unwrap();
})
.unwrap();
}
@@ -114,16 +85,16 @@ pub struct Battery {
}
impl Battery {
pub fn new(events: Arc<Mutex<Events>>, font: &'static str, size: f32) -> BarWidget {
pub fn new(events: Arc<Mutex<Events>>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
let battery = Battery {
inner: Arc::new(Mutex::new(InnerBattery {
value: 0.,
state: UpowerBatteryState::Unknown,
state: BatteryState::Unknown,
dirty: false,
})),
};
start_monitor(battery.inner.clone(), events);
BarWidget::new(Box::new(battery), font, size)
BarWidget::new(Box::new(battery), fm, font, size)
}
}
@@ -142,20 +113,14 @@ impl BarWidgetImpl for Battery {
fn color(&self) -> Color {
let inner = self.inner.lock().unwrap();
match inner.state {
UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => {
if inner.value > 0.25 {
Color::new(1.0, 1.0, 1.0, 1.0)
} else if inner.value > 0.1 {
Color::new(1.0, 0.5, 0.0, 1.0)
} else {
Color::new(1.0, 0.0, 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)
BatteryState::Charging | BatteryState::FullyCharged => Color::LIGHTGREEN,
BatteryState::PendingCharge => Color::LIGHTRED,
_ => if inner.value > 0.25 {
Color::WHITE
} else if inner.value > 0.1 {
Color::DARKORANGE
} else {
Color::RED
}
}
}
+116 -98
View File
@@ -4,7 +4,7 @@ use crate::{
buffer::BufferView,
color::Color,
fonts::FontMap,
state::Event,
event::Event,
widgets::{Geometry, Widget},
};
@@ -14,18 +14,32 @@ pub struct Calendar {
font: &'static str,
sections_x: i32,
sections_y: i32,
shown_sections_x: u32,
shown_sections_y: u32,
size: f32,
dirty: bool,
geometry: Geometry,
}
const DAY_FACTOR: f32 = 1.;
const YEAR_FACTOR: f32 = 1.5;
const MONTH_FACTOR: f32 = 2.;
const DATE_FACTOR: f32 = 2.;
const LINE_HEIGHT: f32 = 1.8;
impl Calendar {
pub fn new(font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar {
pub fn new(fm: &mut FontMap, font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar {
fm.queue_font(font, size, "ADEFHIMNORSTUW");
fm.queue_font(font, size * 1.5, "0123456789");
fm.queue_font(font, size * 2.0, "0123456789 ADFJMOSabcehgilmnoprstuvy");
Calendar {
font,
size,
sections_x,
sections_y,
shown_sections_x: 0,
shown_sections_y: 0,
dirty: true,
geometry: Default::default(),
}
@@ -38,10 +52,62 @@ impl Calendar {
orig: NaiveDate,
mut time: NaiveDate,
) {
let black = Color::new(0., 0., 0., 1.);
let white = Color::new(1., 1., 1., 1.);
let dim = Color::new(0.8, 0.8, 0.8, 1.);
let dimmer = Color::new(0.75, 0.75, 0.75, 1.);
let black = Color::BLACK;
let white = Color::WHITE;
let dim = Color::GREY80;
let dimmer = Color::GREY75;
let mut y_off = 1;
// TODO: Fix offsets:
// - Headline font is not correctly accounted for
//
// Draw the week day
//
for idx in 1..8 {
let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR);
let wk_chr = match idx {
1 => "MON",
2 => "TUE",
3 => "WED",
4 => "THU",
5 => "FRI",
6 => "SAT",
7 => "SUN",
_ => panic!("impossible value"),
};
day_font
.draw_text(
&mut view
.offset((
idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32,
y_off * (self.size * LINE_HEIGHT).ceil() as u32
+ (self.size * 1.).ceil() as u32,
)),
black,
white,
&wk_chr,
)
.unwrap();
}
//
// Draw the month
//
if time.year() != orig.year() {
let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR);
year_font
.draw_text(
&mut view.offset(((self.size * 17.).ceil() as u32, 0)),
black,
dim,
&format!("{:}", time.year()),
)
.unwrap();
}
let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR);
let month_str = match time.month() {
1 => "January",
2 => "February",
@@ -57,62 +123,8 @@ impl Calendar {
12 => "December",
_ => panic!("impossible value"),
};
let mut y_off = 1;
// TODO: Fix offsets:
// - Headline font is not correctly accounted for
//
// Draw the week day
//
for idx in 1..8 {
let day_font = fonts.get_font(self.font, self.size);
let wk_chr = match idx {
1 => "MON",
2 => "TUE",
3 => "WED",
4 => "THU",
5 => "FRI",
6 => "SAT",
7 => "SUN",
_ => panic!("impossible value"),
};
day_font
.auto_draw_text(
&mut view
.offset((
idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32,
y_off * (self.size * 1.8).ceil() as u32
+ (self.size * 1.).ceil() as u32,
))
.unwrap(),
&black,
&white,
&wk_chr,
)
.unwrap();
}
//
// Draw the month
//
if time.year() != orig.year() {
let year_font = fonts.get_font(self.font, self.size * 1.5);
year_font
.auto_draw_text(
&mut view.offset(((self.size * 17.).ceil() as u32, 0)).unwrap(),
&black,
&dim,
&format!("{:}", time.year()),
)
.unwrap();
}
let cal_font = fonts.get_font(self.font, self.size * 2.);
cal_font
.auto_draw_text(&mut view, &black, &white, month_str)
.draw_text(&mut view, black, white, month_str)
.unwrap();
let mut done = false;
@@ -133,16 +145,15 @@ impl Calendar {
//
let wk = time.iso_week();
cal_font
.auto_draw_text(
.draw_text(
&mut view
.offset((
0,
y_off * (self.size * 1.8).ceil() as u32
+ (self.size * 2.).ceil() as u32,
))
.unwrap(),
&black,
&dimmer,
y_off * (self.size * LINE_HEIGHT).ceil() as u32
+ (self.size * MONTH_FACTOR).ceil() as u32,
)),
black,
dimmer,
&format!("{:02}", wk.week()),
)
.unwrap();
@@ -153,21 +164,21 @@ impl Calendar {
//
while x_pos < 8 {
let c = if time.day() == orig.day() && time.month() == orig.month() {
Color::new(1.0, 1.0, 1.0, 1.0)
Color::WHITE
} else {
Color::new(0.5, 0.5, 0.5, 1.0)
Color::GREY50
};
cal_font
.auto_draw_text(
.draw_text(
&mut view
.offset((
x_pos * (self.size * 2.5).ceil() as u32,
y_off * (self.size * 1.8).ceil() as u32 + (self.size * 2.) as u32,
))
.unwrap(),
&black,
&c,
y_off * (self.size * LINE_HEIGHT).ceil() as u32
+ (self.size * MONTH_FACTOR).ceil() as u32,
)),
black,
c,
&format!("{:02}", time.day()),
)
.unwrap();
@@ -205,35 +216,33 @@ impl<'a> Widget for Calendar {
}
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
if self.shown_sections_y == 0 || self.shown_sections_x == 0 {
return Geometry {
x: self.geometry.x,
y: self.geometry.y,
width: 0,
height: 0,
};
}
let time = Local::now().naive_local().date();
let cal_height = (self.size * 16.25).ceil() as u32;
let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32;
let cal_width = (7. * self.size * 2.5 + self.size * 2.).ceil() as u32;
let cal_pad = (3. * self.size).ceil() as u32;
let sections_y = if self.sections_y > 0 {
self.sections_y as u32
} else {
self.geometry.height / cal_height
};
let sections_x = if self.sections_x > 0 {
self.sections_x as u32
} else {
self.geometry.width / (cal_height + cal_pad)
};
let mut t = time.with_day(1).unwrap();
let cals = sections_y * sections_x - 1;
if cals >=3 {
let cals = self.shown_sections_y * self.shown_sections_x;
if cals >= 3 {
t = t.pred_opt().unwrap().with_day(1).unwrap();
}
for ydx in 0..sections_y {
for idx in 0..sections_x {
for ydx in 0..self.shown_sections_y {
for idx in 0..self.shown_sections_x {
self.draw_month(
fonts,
&mut view
.offset(((cal_width + cal_pad) * idx, (cal_height) * ydx))
.unwrap(),
.offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)),
time,
t,
);
@@ -249,17 +258,26 @@ impl<'a> Widget for Calendar {
}
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32;
let cal_height = (self.size * 16.25).ceil() as u32; // TODO: Calculate from font sizes
let cal_width = 7 * (self.size * 2.5).ceil() as u32 + (self.size * 2.).ceil() as u32;
let cal_height = (self.size * MONTH_FACTOR).ceil() as u32
+ (self.size * LINE_HEIGHT * 8.).ceil() as u32;
let cal_pad = (self.size * 3.).ceil() as u32;
let width = if self.sections_x > 0 {
let possible_sections_x = geometry.width / (cal_width + cal_pad);
let possible_sections_y = geometry.height / cal_height;
let width = if self.sections_x > 0 && self.sections_x < possible_sections_x as i32 {
self.shown_sections_x = self.sections_x as u32;
cal_width * self.sections_x as u32 + cal_pad * (self.sections_x as u32 - 1)
} else {
(geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad)
self.shown_sections_x = possible_sections_x;
(geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad)
};
let height = if self.sections_y > 0 {
let height = if self.sections_y > 0 && self.sections_y < possible_sections_y as i32 {
self.shown_sections_y = self.sections_y as u32;
cal_height * self.sections_y as u32
} else {
self.shown_sections_y = possible_sections_y;
(geometry.height / cal_height) * cal_height
};
self.geometry = Geometry {
+7 -6
View File
@@ -4,7 +4,7 @@ use crate::{
buffer::BufferView,
color::Color,
fonts::FontMap,
state::Event,
event::Event,
widgets::{Geometry, Widget},
};
@@ -20,7 +20,8 @@ pub struct Clock {
}
impl<'a> Clock {
pub fn new(font: &'static str, size: f32) -> Clock {
pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Clock {
fm.queue_font(font, size, "0123456789: ");
Clock {
font,
size,
@@ -52,16 +53,16 @@ impl<'a> Widget for Clock {
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 fg = Color::WHITE;
let bg = Color::BLACK;
let digit = self.digit_width;
let colon = self.colon_width;
let font = fonts.get_font(self.font, self.size);
font.draw_text_fixed_width(
view,
&bg,
&fg,
bg,
fg,
&[digit, digit, colon, digit, digit],
&format!("{:02}:{:02}", time.hour(), time.minute()),
)
+12 -11
View File
@@ -4,7 +4,7 @@ use crate::{
buffer::BufferView,
color::Color,
fonts::FontMap,
state::Event,
event::Event,
widgets::{Geometry, Widget},
};
@@ -18,7 +18,8 @@ pub struct Date {
}
impl Date {
pub fn new(font: &'static str, size: f32) -> Date {
pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Date {
fm.queue_font(font, size, "0123456789/, adehinortuFMSTW");
Date {
font,
size,
@@ -48,14 +49,13 @@ impl<'a> Widget for Date {
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 fg = Color::WHITE;
let bg = Color::BLACK;
let font = fonts.get_font(self.font, self.size);
font.auto_draw_text(
&mut date_line,
&bg,
&fg,
font.draw_text(
view,
bg,
fg,
&format!(
"{}, {:02}/{:02}/{:4}",
time.weekday(),
@@ -69,13 +69,14 @@ impl<'a> Widget for Date {
self.geometry
}
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.;
let font = fonts.get_font(self.font, self.size);
self.geometry = Geometry {
x: geometry.x,
y: geometry.y,
width: width.ceil() as u32,
height: self.size.ceil() as u32,
height: font.height().ceil() as u32,
};
self.geometry
}
+71 -38
View File
@@ -12,7 +12,7 @@ use crate::{
color::Color,
fonts::FontMap,
keyboard::{keysyms, KeyEvent},
state::Event,
event::{Event, Events, PointerEvent, PointerButton},
utils::desktop::{load_desktop_files, Desktop},
widgets::{Geometry, Widget},
};
@@ -205,14 +205,20 @@ struct Launcher {
}
impl Launcher {
fn new() -> Launcher {
fn new(events: Arc<Mutex<Events>>) -> 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();
});
thread::Builder::new()
.name("desktopini".to_string())
.spawn(move || {
let mut options = options.lock().unwrap();
*options = load_desktop_files();
drop(options);
let mut events = events.lock().unwrap();
events.add_event(Event::LauncherUpdate);
})
.unwrap();
}
Launcher {
@@ -280,31 +286,31 @@ impl InterfaceWidget for Launcher {
fonts: &mut FontMap,
view: &mut BufferView,
) -> Geometry {
let fg = Color::new(1., 1., 1., 1.);
let bg = Color::new(0., 0., 0., 1.);
let fg = Color::WHITE;
let bg = Color::BLACK;
let line_height = intf.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_line = view.offset((0, prompt_offset));
let font = fonts.get_font(intf.font, intf.size);
let mut x_max = font
.auto_draw_text_with_cursor(
&mut prompt_line,
&bg,
&fg,
bg,
fg,
&format!(" {}", &intf.prompt.input),
intf.prompt.cursor + 3,
)
.unwrap();
font.auto_draw_text(&mut prompt_line, &bg, &fg, ">")
font.auto_draw_text(&mut prompt_line, bg, fg, ">")
.unwrap();
// Draw entries
let dimfg = Color::new(0.5, 0.5, 0.5, 1.0);
let dimfg = Color::GREY50;
prompt_offset -= 16;
for (idx, m) in self.matches.iter().enumerate() {
@@ -312,7 +318,7 @@ impl InterfaceWidget for Launcher {
break;
}
prompt_offset -= line_height;
let mut line = view.offset((0, prompt_offset)).unwrap();
let mut line = view.offset((0, prompt_offset));
if idx == intf.selection {
let fuzzy_matcher = SkimMatcherV2::default();
@@ -323,20 +329,20 @@ impl InterfaceWidget for Launcher {
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));
colors.push(Color::LIGHTORANGE);
} else {
colors.push(Color::new(0.75, 0.75, 0.75, 1.0));
colors.push(Color::GREY75);
}
}
x_max = max(
x_max,
font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name)
font.auto_draw_text_individual_colors(&mut line, bg, &colors, &m.name)
.unwrap(),
);
} else {
x_max = max(
x_max,
font.auto_draw_text(&mut line, &bg, &dimfg, &m.name)
font.auto_draw_text(&mut line, bg, dimfg, &m.name)
.unwrap(),
);
}
@@ -379,27 +385,27 @@ impl InterfaceWidget for Shell {
fonts: &mut FontMap,
view: &mut BufferView,
) -> Geometry {
let fg = Color::new(1., 1., 1., 1.);
let bg = Color::new(0., 0., 0., 1.);
let fg = Color::WHITE;
let bg = Color::BLACK;
let line_height = intf.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_line = view.offset((0, prompt_offset));
// Draw prompt
let font = fonts.get_font(intf.font, intf.size);
let x_max = font
.auto_draw_text_with_cursor(
&mut prompt_line,
&bg,
&fg,
bg,
fg,
&format!(" {}", &intf.prompt.input),
intf.prompt.cursor + 3,
)
.unwrap();
font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "!")
font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "!")
.unwrap();
Geometry {
@@ -440,45 +446,45 @@ impl InterfaceWidget for Calc {
fonts: &mut FontMap,
view: &mut BufferView,
) -> Geometry {
let fg = Color::new(1., 1., 1., 1.);
let bg = Color::new(0., 0., 0., 1.);
let fg = Color::WHITE;
let bg = Color::BLACK;
let line_height = intf.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_line = view.offset((0, prompt_offset));
// Draw prompt
let font = fonts.get_font(intf.font, intf.size);
let res_off = font
.auto_draw_text_with_cursor(
&mut prompt_line,
&bg,
&fg,
bg,
fg,
&format!(" {} ", &intf.prompt.input),
intf.prompt.cursor + 3,
)
.unwrap();
font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "=")
font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "=")
.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))
let resfg = Color::LIGHTORANGE;
let mut result_line = view.offset((res_off.0, prompt_offset));
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);
let dimfg = Color::GREY50;
if prompt_offset < line_height {
break;
}
prompt_offset -= line_height;
let mut result_line = view.offset((0, prompt_offset)).unwrap();
font.auto_draw_text(&mut result_line, &bg, &dimfg, &m)
let mut result_line = view.offset((0, prompt_offset));
font.auto_draw_text(&mut result_line, bg, dimfg, &m)
.unwrap();
}
@@ -512,9 +518,14 @@ pub struct Interface {
}
impl Interface {
pub fn new(font: &'static str, size: f32) -> Interface {
pub fn new(events: Arc<Mutex<Events>>, fm: &mut FontMap, font: &'static str, size: f32) -> Interface {
fm.queue_font(
font, size,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 >=!,.-/()",
);
Interface {
launcher: Launcher::new(),
launcher: Launcher::new(events),
shell: Shell {},
calc: Calc {
old: Vec::new(),
@@ -531,6 +542,23 @@ impl Interface {
}
}
fn pointer_input(&mut self, event: &PointerEvent) {
if let PointerButton::Left = event.button {
let line_height = self.inner.size.ceil() as u32;
let height = self.inner.geometry.height - line_height - 16;
let lines = height / line_height;
let offset = height % line_height;
let pos = if event.pos.1 >= offset {
event.pos.1 - offset
} else {
return;
};
self.inner.selection = (lines - pos / line_height - 1) as usize;
self.inner.dirty = true;
}
}
fn keyboard_input(&mut self, event: &KeyEvent) {
if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) {
return;
@@ -616,6 +644,11 @@ impl Widget for Interface {
fn event(&mut self, event: &Event) {
match event {
Event::KeyEvent(e) => self.keyboard_input(e),
Event::PointerEvent(e) => self.pointer_input(e),
Event::LauncherUpdate => {
self.inner.dirty = true;
self.launcher.update(&self.inner);
},
Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()),
_ => (),
}
+174
View File
@@ -0,0 +1,174 @@
use std::cmp::max;
use crate::{
fonts::FontMap,
state::State,
widgets::{Geometry},
};
pub trait Layout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry;
}
pub struct HorizontalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl HorizontalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(HorizontalLayout { widgets })
}
}
impl Layout for HorizontalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.geometry_update(fonts, &geo, state);
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 {
pub widgets: Vec<Box<dyn Layout>>,
}
impl VerticalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(VerticalLayout { widgets })
}
}
impl Layout for VerticalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.geometry_update(fonts, &geo, state);
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 IndexedLayout {
pub fn new(widget_idx: usize) -> Box<dyn Layout> {
Box::new(IndexedLayout { widget_idx })
}
}
impl Layout for IndexedLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
state.geometry_update(self.widget_idx, fonts, geometry)
}
}
pub struct Margin {
pub widget: Box<dyn Layout>,
pub margin: (u32, u32, u32, u32),
}
impl Margin {
pub fn new(widget: Box<dyn Layout>, margin: (u32, u32, u32, u32)) -> Box<dyn Layout> {
Box::new(Margin { widget, margin })
}
}
impl Layout for Margin {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> 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, state);
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,
}
}
}
pub struct InvertedHorizontalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl InvertedHorizontalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(InvertedHorizontalLayout { widgets })
}
}
impl Layout for InvertedHorizontalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_height = 0;
for w in self.widgets.iter() {
let mut temp_geo = geo.clone();
let temp_result = w.geometry_update(fonts, &temp_geo, state);
temp_geo.x = geo.x + (geo.width - temp_result.width);
let result = w.geometry_update(fonts, &temp_geo, state);
geo.width -= result.width;
max_height = max(result.height, max_height);
}
geo.height = max_height;
geo
}
}
+8 -6
View File
@@ -7,14 +7,17 @@ use crate::{
widgets::{Geometry, Widget},
};
#[derive(Default)]
pub struct Line {
geometry: Geometry,
thickness: u32,
}
impl Line {
pub fn new() -> Line {
Default::default()
pub fn new(thickness: u32) -> Line {
Line{
geometry: Default::default(),
thickness,
}
}
}
@@ -23,16 +26,15 @@ impl Widget for Line {
self.geometry
}
fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
view.memset(&Color::new(1., 1., 1., 1.));
view.memset(Color::WHITE);
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,
height: self.thickness,
};
self.geometry
}
+5 -4
View File
@@ -1,3 +1,4 @@
mod audio;
mod backlight;
mod bar_widget;
mod battery;
@@ -7,7 +8,9 @@ mod date;
mod launcher;
mod line;
mod widget;
mod layout;
pub use audio::Audio;
pub use backlight::Backlight;
pub use battery::Battery;
pub use calendar::Calendar;
@@ -15,7 +18,5 @@ pub use clock::Clock;
pub use date::Date;
pub use launcher::Interface;
pub use line::Line;
pub use widget::{
Geometry, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin,
VerticalLayout, Widget, WidgetUpdater,
};
pub use layout::{VerticalLayout, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, WidgetUpdater};
pub use widget::{Geometry, Widget};
+6 -167
View File
@@ -6,7 +6,7 @@ use std::{
use crate::{
buffer::BufferView,
fonts::FontMap,
state::{Event, State},
event::Event,
};
#[derive(Debug, Default, Clone, Copy)]
@@ -47,6 +47,11 @@ impl Geometry {
height: max(self.height + self.y, other.height + other.height) - y,
}
}
pub fn contains(&self, position: (u32, u32)) -> bool {
position.0 >= self.x && position.0 < self.x + self.width &&
position.1 >= self.y && position.1 < self.y + self.height
}
}
pub trait Widget {
@@ -59,169 +64,3 @@ pub trait Widget {
fn event(&mut self, _event: &Event) {}
}
pub trait Layout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry;
}
pub struct HorizontalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl HorizontalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(HorizontalLayout { widgets })
}
}
impl Layout for HorizontalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.geometry_update(fonts, &geo, state);
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 {
pub widgets: Vec<Box<dyn Layout>>,
}
impl VerticalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(VerticalLayout { widgets })
}
}
impl Layout for VerticalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.geometry_update(fonts, &geo, state);
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 IndexedLayout {
pub fn new(widget_idx: usize) -> Box<dyn Layout> {
Box::new(IndexedLayout { widget_idx })
}
}
impl Layout for IndexedLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
state.geometry_update(self.widget_idx, fonts, geometry)
}
}
pub struct Margin {
pub widget: Box<dyn Layout>,
pub margin: (u32, u32, u32, u32),
}
impl Margin {
pub fn new(widget: Box<dyn Layout>, margin: (u32, u32, u32, u32)) -> Box<dyn Layout> {
Box::new(Margin { widget, margin })
}
}
impl Layout for Margin {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> 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, state);
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,
}
}
}
pub struct InvertedHorizontalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl InvertedHorizontalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(InvertedHorizontalLayout { widgets })
}
}
impl Layout for InvertedHorizontalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_height = 0;
for w in self.widgets.iter() {
let mut temp_geo = geo.clone();
let temp_result = w.geometry_update(fonts, &temp_geo, state);
temp_geo.x = geo.x + (geo.width - temp_result.width);
let result = w.geometry_update(fonts, &temp_geo, state);
geo.width -= result.width;
max_height = max(result.height, max_height);
}
geo.height = max_height;
geo
}
}