From 7593cd48a41e42f4679c1e1f67061adf9738969b Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 16 Jan 2023 00:25:34 +0100 Subject: [PATCH] Add calendar, refactor more --- Cargo.lock | 28 ---- Cargo.toml | 6 +- src/buffer.rs | 6 + src/fonts.rs | 16 +-- src/main.rs | 167 ++++++++++++----------- src/state.rs | 135 +++++++++++++------ src/widgets/backlight.rs | 8 -- src/widgets/bar_widget.rs | 13 +- src/widgets/battery.rs | 29 ++-- src/widgets/calendar.rs | 273 ++++++++++++++++++++++++++++++++++++++ src/widgets/clock.rs | 17 ++- src/widgets/date.rs | 17 ++- src/widgets/launcher.rs | 120 +++++++++-------- src/widgets/mod.rs | 6 +- src/widgets/widget.rs | 134 +++++++++++++------ 15 files changed, 689 insertions(+), 286 deletions(-) create mode 100644 src/widgets/calendar.rs diff --git a/Cargo.lock b/Cargo.lock index de1ca1d..6b33d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,12 +105,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -243,15 +237,6 @@ dependencies = [ "instant", ] -[[package]] -name = "fontconfig" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b0653582d9c82645f95bacd0dc87b49a3126f7a9633be8cafa9c6d17d525847" -dependencies = [ - "yeslogic-fontconfig-sys", -] - [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -1102,7 +1087,6 @@ dependencies = [ "calloop", "chrono", "dbus", - "fontconfig", "fuzzy-matcher", "itertools", "memmap", @@ -1128,15 +1112,3 @@ dependencies = [ "libc", "memmap2", ] - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec657fd32bbcbeaef5c7bc8e10b3db95b143fab8db0a50079773dbf936fd4f73" -dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 411d477..372eb25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "wldash2" version = "0.1.0" edition = "2021" +[profile.release] +lto = "fat" +panic = "abort" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -11,7 +15,7 @@ wayland-protocols = { version = "0.30", features = ["client", "unstable", "stagi tempfile = "3.3" nix = "0.26" memmap = "0.7" -fontconfig = "0.6.0" +#fontconfig = "0.6.0" rusttype = "^0.9" calloop = { version = "0.10" } chrono = "0.4" diff --git a/src/buffer.rs b/src/buffer.rs index 8a2dc0f..685583b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -125,6 +125,12 @@ pub struct BufferManager { } impl BufferManager { + pub fn new() -> BufferManager { + BufferManager { + buffers: Vec::new(), + } + } + pub fn clear_buffers(&mut self) { for buf in self.buffers.iter_mut() { buf.buffer.destroy() diff --git a/src/fonts.rs b/src/fonts.rs index 49705d3..da6c431 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -1,24 +1,24 @@ //! Utility module for fonts use crate::draw; -use fontconfig::Fontconfig as FontConfig; +//use fontconfig::Fontconfig as FontConfig; use rusttype::Font; use std::{ cell::RefCell, - mem, - rc::Rc, - thread, collections::HashMap, fs::File, hash, io::Read, + mem, path::{Path, PathBuf}, + rc::Rc, + thread, }; - /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; +/* /// FontSeeker is a marker struct that is used to look up fonts pub(crate) struct FontSeeker; @@ -30,7 +30,7 @@ impl FontSeeker { fc.find(name, None).unwrap().path } } - +*/ /// FontLoader is a marker struct that is used to load files pub(crate) struct FontLoader; @@ -113,7 +113,8 @@ impl FontMap { let path = if v.0.starts_with("/") { std::path::PathBuf::from(v.0) } else { - FontSeeker::from_string(v.0) + //FontSeeker::from_string(v.0) + panic!("required FontSeeker"); }; let fontref = Box::leak(Box::new( FontLoader::from_path(path).expect("unable to load font"), @@ -158,4 +159,3 @@ impl MaybeFontMap { } } } - diff --git a/src/main.rs b/src/main.rs index a99243e..a101746 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,11 @@ mod color; mod draw; mod fonts; mod keyboard; +mod state; mod utils; mod widgets; -mod state; -use wayland_client::{Connection, QueueHandle, WaylandSource,}; +use wayland_client::{Connection, QueueHandle, WaylandSource}; use calloop::{ timer::{TimeoutAction, Timer}, @@ -15,27 +15,16 @@ use calloop::{ }; use chrono::{Duration, Local, Timelike}; -use buffer::{BufferManager, BufferView}; +use buffer::BufferView; use color::Color; -use fonts::{MaybeFontMap, FontMap}; -use keyboard::Keyboard; +use fonts::{FontMap, MaybeFontMap}; +use state::{Event, Events, State}; use widgets::{ - Backlight, Battery, Clock, Date, Geometry, Interface, Line, WidgetUpdater, + Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, + Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, }; -use state::State; -use std::thread; - -impl WidgetUpdater for State { - fn geometry_update( - &mut self, - idx: usize, - fonts: &mut FontMap, - geometry: &Geometry, - ) -> Geometry { - self.widgets[idx].geometry_update(fonts, geometry) - } -} +use std::{rc::Rc, thread}; fn main() { let font_thread = thread::spawn(move || { @@ -49,6 +38,21 @@ fn main() { "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", 32., ); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 12., + ); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 12. * 1.5, + ); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 12. * 2.0, + ); fm.load_fonts(); fm }); @@ -60,19 +64,34 @@ fn main() { let display = conn.display(); display.get_registry(&qhandle, ()); - let bufmgr = BufferManager { - buffers: Vec::new(), - }; - let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); + let events = Events::new(ping_sender); + let clock = Box::new(Clock::new("sans", 128.)); +// let calendar = Box::new(Calendar::new("monospace", 12.0, 1, -1)); let date = Box::new(Date::new("sans", 48.)); let line = Box::new(Line::new()); let launcher = Box::new(Interface::new("monospace", 32.)); - let battery = Box::new(Battery::new(ping_sender, "sans", 24.)); + let battery = Box::new(Battery::new(events.clone(), "sans", 24.)); let backlight = Box::new(Backlight::new("intel_backlight", "sans", 24.)); + let widgets: Vec> = + vec![clock, date, battery, backlight, line, launcher]; + let layout = Rc::new(VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + IndexedLayout::new(0), + Margin::new(IndexedLayout::new(1), (16, 8, 0, 0)), + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), + ]), + ]), + IndexedLayout::new(4), + IndexedLayout::new(5), +// InvertedHorizontalLayout::new(vec![IndexedLayout::new(5), IndexedLayout::new(6)]), + ])); + let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -86,7 +105,12 @@ fn main() { handle .insert_source(ping_source, |(), &mut (), state| { - state.widgets[2].set_dirty(true); + let events = state.events.lock().unwrap().flush(); + for widget in state.widgets.iter_mut() { + for event in events.iter() { + widget.event(event); + } + } state.dirty = true; }) .expect("Failed to insert ping source!"); @@ -101,8 +125,9 @@ fn main() { .with_nanosecond(0) .unwrap(); let d = target - now; - state.widgets[0].set_dirty(true); - state.widgets[1].set_dirty(true); + for widget in state.widgets.iter_mut() { + widget.event(&Event::NewMinute); + } state.dirty = true; // The timer event source requires us to return a TimeoutAction to @@ -111,22 +136,7 @@ fn main() { }) .expect("Failed to insert event source!"); - let mut state = State { - running: true, - dirty: true, - activated: false, - base_surface: None, - wm_base: None, - xdg_surface: None, - wl_shm: None, - xdg_activation: None, - configured: false, - dimensions: (320, 240), - bufmgr: bufmgr, - widgets: vec![clock, date, battery, backlight, line, launcher], - keyboard: Keyboard::new(), - fonts: MaybeFontMap::Waiting(font_thread), - }; + let mut state = State::new(widgets, layout, MaybeFontMap::Waiting(font_thread), events); let mut damage = Vec::new(); for _ in 0..state.widgets.len() { @@ -142,7 +152,6 @@ fn main() { continue; } - let mut drew = false; let mut force = false; if state.bufmgr.buffers.len() == 0 { state.add_buffer(&qhandle); @@ -168,43 +177,51 @@ fn main() { let bg = Color::new(0., 0., 0., 1.); let surface = state.base_surface.as_ref().unwrap().clone(); - for (idx, widget) in state.widgets.iter_mut().enumerate() { - if force || widget.get_dirty() { - widget.set_dirty(false); - drew = true; - - let geo = widget.geometry(); - let old_damage = damage[idx]; - - if !force { - bufview.subgeometry(old_damage).unwrap().memset(&bg); - } else { + if force { + for (idx, widget) in state.widgets.iter_mut().enumerate() { + if force || widget.get_dirty() { + let geo = widget.geometry(); bufview.subgeometry(geo).unwrap().memset(&bg); - } - let mut subview = bufview.subgeometry(geo).unwrap(); - let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); - let combined_damage = new_damage.expand(old_damage); - damage[idx] = new_damage; - surface.damage_buffer( - combined_damage.x as i32, - combined_damage.y as i32, - combined_damage.width as i32, - combined_damage.height as i32, - ); - //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + let mut subview = bufview.subgeometry(geo).unwrap(); + damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + } + } + surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) + } else { + let mut drew = false; + for (idx, widget) in state.widgets.iter_mut().enumerate() { + if force || widget.get_dirty() { + drew = true; + + let old_damage = damage[idx]; + bufview.subgeometry(old_damage).unwrap().memset(&bg); + + let geo = widget.geometry(); + let mut subview = bufview.subgeometry(geo).unwrap(); + + let new_damage = + widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + let combined_damage = new_damage.expand(old_damage); + damage[idx] = new_damage; + + surface.damage_buffer( + combined_damage.x as i32, + combined_damage.y as i32, + combined_damage.width as i32, + combined_damage.height as i32, + ); + //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + } + } + if !drew { + buf.release(); + continue; } } - if !drew { - buf.release(); - continue; - } - if force { - surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) - } surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); } } - diff --git a/src/state.rs b/src/state.rs index d22d1e9..506e29f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,12 @@ use std::{ env, process::exit, + rc::Rc, + sync::{Arc, Mutex}, }; +use calloop::ping::Ping; + use wayland_client::{ protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, @@ -18,31 +22,93 @@ use wayland_protocols::xdg::{ use crate::{ buffer::BufferManager, - fonts::MaybeFontMap, - keyboard::Keyboard, - widgets::{ - Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget, - }, + fonts::{FontMap, MaybeFontMap}, + keyboard::{KeyEvent, Keyboard}, + widgets::{Geometry, Layout, Widget, WidgetUpdater}, }; +pub enum Event { + NewMinute, + PowerUpdate, + KeyEvent(KeyEvent), + TokenUpdate(String), +} + +pub struct Events { + dirty: bool, + events: Vec, + ping: Ping, +} + +impl Events { + pub fn new(ping: Ping) -> Arc> { + Arc::new(Mutex::new(Events { + dirty: false, + events: Vec::new(), + ping, + })) + } + + pub fn add_event(&mut self, ev: Event) { + self.events.push(ev); + if !self.dirty { + self.dirty = true; + self.ping.ping(); + } + } + + pub fn flush(&mut self) -> Vec { + self.dirty = false; + self.events.drain(..).collect() + } +} + pub struct State { pub running: bool, pub dirty: bool, - pub activated: bool, + activated: bool, pub base_surface: Option, - pub wm_base: Option, - pub xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, - pub wl_shm: Option, - pub xdg_activation: Option, + wm_base: Option, + xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + wl_shm: Option, + xdg_activation: Option, pub configured: bool, pub dimensions: (i32, i32), pub bufmgr: BufferManager, pub widgets: Vec>, - pub keyboard: Keyboard, + keyboard: Keyboard, pub fonts: MaybeFontMap, + pub events: Arc>, + layout: Rc>, } impl State { + pub fn new( + widgets: Vec>, + layout: Rc>, + fonts: MaybeFontMap, + events: Arc>, + ) -> State { + State { + running: true, + dirty: true, + activated: false, + base_surface: None, + wm_base: None, + xdg_surface: None, + wl_shm: None, + xdg_activation: None, + configured: false, + dimensions: (320, 240), + bufmgr: BufferManager::new(), + keyboard: Keyboard::new(), + widgets, + fonts, + events, + layout, + } + } + pub fn add_buffer(&mut self, qh: &QueueHandle) { self.bufmgr.add_buffer( self.wl_shm.as_ref().expect("missing wl_shm"), @@ -68,6 +134,17 @@ impl State { } } +impl WidgetUpdater for State { + fn geometry_update( + &mut self, + idx: usize, + fonts: &mut FontMap, + geometry: &Geometry, + ) -> Geometry { + self.widgets[idx].geometry_update(fonts, geometry) + } +} + impl Dispatch for State { fn event( state: &mut Self, @@ -264,36 +341,9 @@ impl Dispatch for State { if state.dimensions != (width, height) { state.bufmgr.clear_buffers(); state.dimensions = (width, height); - let mut layout = VerticalLayout { - widgets: vec![ - Box::new(HorizontalLayout { - widgets: vec![ - Box::new(IndexedLayout { widget_idx: 0 }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 1 }), - margin: (16, 8, 0, 0), - }), - Box::new(VerticalLayout { - widgets: vec![ - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 2 }), - margin: (16, 8, 8, 0), - }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 3 }), - margin: (16, 8, 8, 0), - }), - ], - }), - ], - }), - Box::new(IndexedLayout { widget_idx: 4 }), - Box::new(IndexedLayout { widget_idx: 5 }), - ], - }; - state.fonts.resolve(); let fonts = state.fonts.unwrap(); + let layout = state.layout.clone(); layout.geometry_update( &mut fonts.borrow_mut(), &Geometry { @@ -355,8 +405,9 @@ impl Dispatch for State { return; } let k = state.keyboard.key(serial, time, key, kbstate); + let ev = Event::KeyEvent(k); for widget in state.widgets.iter_mut() { - widget.keyboard_input(&k); + widget.event(&ev); } state.dirty = true; } @@ -372,6 +423,7 @@ impl Dispatch for State { .modifiers(mods_depressed, mods_latched, mods_locked, group); } wl_keyboard::Event::Keymap { format, fd, size } => { + // TODO: Keymap loading blocks xdg configure, consider delaying state.keyboard.keymap(format, fd, size); } _ => (), @@ -402,8 +454,9 @@ impl Dispatch for State { ) { match event { xdg_activation_token_v1::Event::Done { token } => { + let ev = Event::TokenUpdate(token); for widget in state.widgets.iter_mut() { - widget.token_update(&token); + widget.event(&ev); } } _ => (), diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 02064a9..1226ce8 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -20,7 +20,6 @@ pub struct Backlight { device_path: PathBuf, cur: u64, max: u64, - dirty: bool, } impl Backlight { @@ -34,7 +33,6 @@ impl Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, max: 0, - dirty: false, }; dev.update(); @@ -43,12 +41,6 @@ impl Backlight { } impl BarWidgetImpl for Backlight { - fn get_dirty(&self) -> bool { - self.dirty - } - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty - } fn name(&self) -> &'static str { "backlight" } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index f57febb..e10e23f 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -9,8 +9,9 @@ use crate::{ }; pub trait BarWidgetImpl { - fn get_dirty(&self) -> bool; - fn set_dirty(&mut self, dirty: bool); + fn get_dirty(&self) -> bool { + false + } fn name(&self) -> &'static str; fn value(&self) -> f32; fn color(&self) -> Color; @@ -27,7 +28,9 @@ impl BarWidget { pub fn new(inner_widget: Box, font: &'static str, size: f32) -> BarWidget { BarWidget { geometry: Default::default(), - inner_widget, font, size, + inner_widget, + font, + size, } } } @@ -41,10 +44,6 @@ impl Widget for BarWidget { 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(self.font, self.size); let fg = Color::new(1., 1., 1., 1.); diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index e448871..4eeaea2 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -4,8 +4,6 @@ use std::{ time::Duration, }; -use calloop::ping::Ping; - use dbus::blocking::{ stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, LocalConnection, @@ -13,6 +11,7 @@ use dbus::blocking::{ use crate::{ color::Color, + state::{Event, Events}, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; @@ -28,9 +27,10 @@ enum UpowerBatteryState { struct InnerBattery { value: f32, state: UpowerBatteryState, + dirty: bool, } -fn start_monitor(inner: Arc>, ping: Ping) { +fn start_monitor(inner: Arc>, events: Arc>) { thread::Builder::new() .name("battmon".to_string()) .spawn(move || { @@ -84,11 +84,15 @@ fn start_monitor(inner: Arc>, ping: Ping) { 6 => UpowerBatteryState::Discharging, _ => UpowerBatteryState::Unknown, }; - ping.ping(); + 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.; - ping.ping(); + inner.dirty = true; + let mut events = events.lock().unwrap(); + events.add_event(Event::PowerUpdate); } _ => (), } @@ -107,35 +111,32 @@ fn start_monitor(inner: Arc>, ping: Ping) { pub struct Battery { inner: Arc>, - dirty: bool, } impl Battery { - pub fn new(ping: Ping, font: &'static str, size: f32) -> BarWidget { + pub fn new(events: Arc>, font: &'static str, size: f32) -> BarWidget { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., state: UpowerBatteryState::Unknown, + dirty: false, })), - dirty: false, }; - start_monitor(battery.inner.clone(), ping); + start_monitor(battery.inner.clone(), events); BarWidget::new(Box::new(battery), font, size) } } impl BarWidgetImpl for Battery { fn get_dirty(&self) -> bool { - self.dirty - } - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty + self.inner.lock().unwrap().dirty } fn name(&self) -> &'static str { "battery" } fn value(&self) -> f32 { - let inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock().unwrap(); + inner.dirty = false; inner.value } fn color(&self) -> Color { diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs new file mode 100644 index 0000000..f16f96f --- /dev/null +++ b/src/widgets/calendar.rs @@ -0,0 +1,273 @@ +use std::default::Default; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + state::Event, + widgets::{Geometry, Widget}, +}; + +use chrono::{Datelike, Local, NaiveDate}; + +pub struct Calendar { + font: &'static str, + sections_x: i32, + sections_y: i32, + size: f32, + dirty: bool, + geometry: Geometry, +} + +impl Calendar { + pub fn new(font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { + Calendar { + font, + size, + sections_x, + sections_y, + dirty: true, + geometry: Default::default(), + } + } + + fn draw_month( + &mut self, + fonts: &mut FontMap, + mut view: &mut BufferView, + 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 month_str = match time.month() { + 1 => "January", + 2 => "February", + 3 => "March", + 4 => "April", + 5 => "May", + 6 => "June", + 7 => "July", + 8 => "August", + 9 => "September", + 10 => "October", + 11 => "November", + 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) + .unwrap(); + + let mut done = false; + while !done { + let mut x_pos = 0; + + // + // Find the start of this week + // + let mut wkday = time.weekday(); + while wkday != chrono::Weekday::Mon { + x_pos += 1; + wkday = wkday.pred(); + } + + // + // Draw the week number + // + let wk = time.iso_week(); + cal_font + .auto_draw_text( + &mut view + .offset(( + 0, + y_off * (self.size * 1.8).ceil() as u32 + + (self.size * 2.).ceil() as u32, + )) + .unwrap(), + &black, + &dimmer, + &format!("{:02}", wk.week()), + ) + .unwrap(); + x_pos += 1; + + // + // Draw the dates + // + 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) + } else { + Color::new(0.5, 0.5, 0.5, 1.0) + }; + + cal_font + .auto_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, + &format!("{:02}", time.day()), + ) + .unwrap(); + + let t = time.with_day(time.day() + 1); + if t.is_none() { + done = true; + break; + } + time = t.unwrap(); + x_pos += 1; + } + + y_off += 1; + } + } +} + +impl<'a> Widget for Calendar { + fn get_dirty(&self) -> bool { + self.dirty + } + + fn geometry(&self) -> Geometry { + self.geometry + } + + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), + } + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + 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_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 { + t = t.pred_opt().unwrap().with_day(1).unwrap(); + } + + for ydx in 0..sections_y { + for idx in 0..sections_x { + self.draw_month( + fonts, + &mut view + .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)) + .unwrap(), + time, + t, + ); + t = if t.month() == 12 { + t.with_year(t.year() + 1).unwrap().with_month(1).unwrap() + } else { + t.with_month(t.month() + 1).unwrap() + }; + } + } + self.dirty = false; + self.geometry + } + + 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_pad = (self.size * 3.).ceil() as u32; + let width = if self.sections_x > 0 { + 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) + }; + let height = if self.sections_y > 0 { + cal_height * self.sections_y as u32 + } else { + (geometry.height / cal_height) * cal_height + }; + self.geometry = Geometry { + x: geometry.x, + y: geometry.y, + width: width, + height: height, + }; + self.geometry + } +} diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 63ee961..0fb807c 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -4,6 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, + state::Event, widgets::{Geometry, Widget}, }; @@ -21,7 +22,8 @@ pub struct Clock { impl<'a> Clock { pub fn new(font: &'static str, size: f32) -> Clock { Clock { - font, size, + font, + size, dirty: true, geometry: Default::default(), digit_width: 0, @@ -31,9 +33,6 @@ impl<'a> Clock { } impl<'a> Widget for Clock { - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty; - } fn get_dirty(&self) -> bool { self.dirty } @@ -42,6 +41,15 @@ impl<'a> Widget for Clock { self.geometry } + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), + } + } + 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.); @@ -58,6 +66,7 @@ impl<'a> Widget for Clock { &format!("{:02}:{:02}", time.hour(), time.minute()), ) .unwrap(); + self.dirty = false; self.geometry } diff --git a/src/widgets/date.rs b/src/widgets/date.rs index b56c5f0..1c9d806 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -4,6 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, + state::Event, widgets::{Geometry, Widget}, }; @@ -19,7 +20,8 @@ pub struct Date { impl Date { pub fn new(font: &'static str, size: f32) -> Date { Date { - font, size, + font, + size, dirty: true, geometry: Default::default(), } @@ -27,9 +29,6 @@ impl Date { } impl<'a> Widget for Date { - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty; - } fn get_dirty(&self) -> bool { self.dirty } @@ -38,6 +37,15 @@ impl<'a> Widget for Date { self.geometry } + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), + } + } + 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.); @@ -57,6 +65,7 @@ impl<'a> Widget for Date { ), ) .unwrap(); + self.dirty = false; self.geometry } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 7afa221..80c9e82 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -1,5 +1,5 @@ use std::{ - cmp::{min, max, Ordering}, + cmp::{max, min, Ordering}, collections::HashMap, default::Default, process::{exit, Command}, @@ -12,6 +12,7 @@ use crate::{ color::Color, fonts::FontMap, keyboard::{keysyms, KeyEvent}, + state::Event, utils::desktop::{load_desktop_files, Desktop}, widgets::{Geometry, Widget}, }; @@ -287,18 +288,17 @@ impl InterfaceWidget for Launcher { // 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); let font = fonts.get_font(intf.font, intf.size); - let mut x_max = font.auto_draw_text_with_cursor( - &mut prompt_line, - &bg, - &fg, - &format!(" {}", &intf.prompt.input), - intf.prompt.cursor + 3, - ) - .unwrap(); + let mut x_max = font + .auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {}", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); font.auto_draw_text(&mut prompt_line, &bg, &fg, ">") .unwrap(); @@ -328,11 +328,17 @@ impl InterfaceWidget for Launcher { colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); } } - x_max = max(x_max, font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) - .unwrap()); + x_max = max( + x_max, + 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) - .unwrap()); + x_max = max( + x_max, + font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) + .unwrap(), + ); } } @@ -343,7 +349,7 @@ impl InterfaceWidget for Launcher { Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - content_height, - width: x_max.0+1, + width: x_max.0 + 1, height: content_height, } } @@ -381,19 +387,18 @@ impl InterfaceWidget for Shell { // 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(intf.font, intf.size); - let x_max = font.auto_draw_text_with_cursor( - &mut prompt_line, - &bg, - &fg, - &format!(" {}", &intf.prompt.input), - intf.prompt.cursor + 3, - ) - .unwrap(); + let x_max = font + .auto_draw_text_with_cursor( + &mut prompt_line, + &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.), "!") .unwrap(); @@ -443,8 +448,6 @@ impl InterfaceWidget for Calc { // 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(intf.font, intf.size); @@ -518,7 +521,8 @@ impl Interface { result: None, }, inner: InnerInterface { - font, size, + font, + size, dirty: false, geometry: Default::default(), selection: 0, @@ -526,32 +530,6 @@ impl Interface { }, } } -} - -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) { @@ -610,8 +588,36 @@ impl Widget for Interface { } } } +} - fn token_update(&mut self, token: &str) { - self.launcher.next_token = Some(token.to_string()); +impl Widget for Interface { + 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 { + self.inner.dirty = false; + 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 event(&mut self, event: &Event) { + match event { + Event::KeyEvent(e) => self.keyboard_input(e), + Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()), + _ => (), + } } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 46f7cb4..ac08074 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,6 +1,7 @@ mod backlight; mod bar_widget; mod battery; +mod calendar; mod clock; mod date; mod launcher; @@ -9,11 +10,12 @@ mod widget; pub use backlight::Backlight; pub use battery::Battery; +pub use calendar::Calendar; 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, + Geometry, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, + VerticalLayout, Widget, WidgetUpdater, }; diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index a5fef27..c63f8e6 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -1,7 +1,13 @@ -use std::cmp::{max, min}; -use std::fmt; +use std::{ + cmp::{max, min}, + fmt, +}; -use crate::{buffer::BufferView, fonts::FontMap, keyboard::KeyEvent}; +use crate::{ + buffer::BufferView, + fonts::FontMap, + state::{Event, State}, +}; #[derive(Debug, Default, Clone, Copy)] pub struct Geometry { @@ -47,38 +53,42 @@ 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) {} + fn event(&mut self, _event: &Event) {} } -pub trait Layout { +pub trait Layout { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + state: &mut State, ) -> Geometry; } -pub struct HorizontalLayout { - pub widgets: Vec>>, +pub struct HorizontalLayout { + pub widgets: Vec>, } -impl Layout for HorizontalLayout { +impl HorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(HorizontalLayout { widgets }) + } +} + +impl Layout for HorizontalLayout { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + 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_mut() { - let result = w.geometry_update(fonts, &geo, user_data); + 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; @@ -90,22 +100,28 @@ impl Layout for HorizontalLayout { } } -pub struct VerticalLayout { - pub widgets: Vec>>, +pub struct VerticalLayout { + pub widgets: Vec>, } -impl Layout for VerticalLayout { +impl VerticalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(VerticalLayout { widgets }) + } +} + +impl Layout for VerticalLayout { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + 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_mut() { - let result = w.geometry_update(fonts, &geo, user_data); + 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); @@ -126,28 +142,40 @@ pub struct IndexedLayout { pub widget_idx: usize, } -impl Layout 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) +impl IndexedLayout { + pub fn new(widget_idx: usize) -> Box { + Box::new(IndexedLayout { widget_idx }) } } -pub struct Margin { - pub widget: Box>, +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, pub margin: (u32, u32, u32, u32), } -impl Layout for Margin { +impl Margin { + pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { + Box::new(Margin { widget, margin }) + } +} + +impl Layout for Margin { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + state: &mut State, ) -> Geometry { let geo = Geometry { x: geometry.x + self.margin.0, @@ -156,7 +184,7 @@ impl Layout for Margin { height: geometry.height - self.margin.1 - self.margin.3, }; - let out = self.widget.geometry_update(fonts, &geo, user_data); + let out = self.widget.geometry_update(fonts, &geo, state); Geometry { x: out.x - self.margin.0, y: out.y - self.margin.1, @@ -165,3 +193,35 @@ impl Layout for Margin { } } } + +pub struct InvertedHorizontalLayout { + pub widgets: Vec>, +} + +impl InvertedHorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + 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 + } +}