Skip to content

Commit

Permalink
feat: add import of config files and introduce color theme (#58)
Browse files Browse the repository at this point in the history
* refactor: generalize CachedLayouts into CachedData

Also fixed the immediate banner redrawing after updating layout.

* feat!: move color definition into `theme`

* refactor!: move `font_size` from `general` to TextProperty

* fix: remove annoying messages about non-existent SVG by check

* feat: introduce config importing using `use` keyword in TOML

* refactor: move theme's definition to any place in config file

* docs: add new section about `themes` and `imports`, reword sentences

* docs: update information about moved font_size

* docs: fix <hr> tag

* docs: fix links and paragraph

* docs: use brand name instead of 'Our'
  • Loading branch information
JarKz authored Dec 13, 2024
1 parent ecbbfe3 commit 9fbe317
Show file tree
Hide file tree
Showing 20 changed files with 1,014 additions and 641 deletions.
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Noti uses a TOML configuration file located at:

```toml
[general]
font = [ "JetBrainsMono Nerd Font", 16 ]
font = "JetBrainsMono Nerd Font"
anchor = "top-right"
offset = [15, 15]
gap = 10
Expand All @@ -71,21 +71,13 @@ width = 300
height = 150

[display]
theme = "pastel"
padding = 8
timeout = 2000

[display.colors.normal]
background = "#1e1e2e"
foreground = "#99AEB3"

[display.colors.critical]
background = "#EBA0AC"
foreground = "#1E1E2E"

[display.border]
size = 4
radius = 10
color = "#000"

[display.image]
max_size = 64
Expand All @@ -100,10 +92,25 @@ ellipsize_at = "middle"
[display.title]
style = "bold italic"
margin = { top = 5 }
font_size = 18

[display.body]
justification = "left"
margin = { top = 12 }
font_size = 16

[[theme]]
name = "pastel"

[theme.normal]
background = "#1e1e2e"
foreground = "#99AEB3"
border = "#000"

[theme.critical]
background = "#EBA0AC"
foreground = "#1E1E2E"
border = "#000"

[[app]]
name = "Telegram Desktop"
Expand Down
20 changes: 11 additions & 9 deletions crates/backend/src/banner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::time;
use std::{path::PathBuf, time};

use config::{Border, Config, DisplayConfig};
use config::{
display::{Border, DisplayConfig},
Config,
};
use dbus::notification::Notification;
use log::{debug, trace};

Expand All @@ -14,8 +17,9 @@ use render::{
WidgetConfiguration,
},
};
use shared::cached_data::CachedData;

use crate::cache::{CachedLayout, CachedLayouts};
use crate::cache::CachedLayout;

pub struct BannerRect {
data: Notification,
Expand Down Expand Up @@ -75,7 +79,7 @@ impl BannerRect {
&mut self,
font_collection: &FontCollection,
config: &Config,
cached_layouts: &CachedLayouts,
cached_layouts: &CachedData<PathBuf, CachedLayout>,
) {
debug!("Banner (id={}): Beginning of draw", self.data.id);

Expand All @@ -88,23 +92,21 @@ impl BannerRect {
let mut drawer = Drawer::new(Bgra::new(), rect_size.clone());

let mut layout = match &display.layout {
config::Layout::Default => Self::default_layout(display),
config::Layout::FromPath { path_buf } => cached_layouts
config::display::Layout::Default => Self::default_layout(display),
config::display::Layout::FromPath { path_buf } => cached_layouts
.get(path_buf)
.and_then(CachedLayout::layout)
.cloned()
.unwrap_or_else(|| Self::default_layout(display)),
};

let font_size = config.general().font.size as f32;

layout.compile(
rect_size,
&WidgetConfiguration {
display_config: display,
theme: config.theme_by_app(&self.data.app_name),
notification: &self.data,
font_collection,
font_size,
override_properties: display.layout.is_default(),
},
);
Expand Down
102 changes: 29 additions & 73 deletions crates/backend/src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,102 +1,58 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use std::path::{Path, PathBuf};

use log::{error, warn};
use log::warn;
use render::widget::Widget;
use shared::file_watcher::{FileState, FilesWatcher};
use shared::{
cached_data::{CacheUpdate, CachedValueError},
file_watcher::{FileState, FilesWatcher},
};

pub(super) struct CachedLayouts {
layouts: HashMap<PathBuf, CachedLayout>,
pub(super) struct CachedLayout {
watcher: FilesWatcher,
layout: Option<Widget>,
}

impl CachedLayouts {
pub(super) fn get(&self, path: &PathBuf) -> Option<&CachedLayout> {
self.layouts.get(path)
}

pub(super) fn update(&mut self) {
self.layouts
.values_mut()
.for_each(|layout| match layout.check_updates() {
FileState::Updated => layout.update(),
FileState::NothingChanged | FileState::NotFound => (),
})
impl CachedLayout {
pub(super) fn layout(&self) -> Option<&Widget> {
self.layout.as_ref()
}

pub(super) fn extend_by_paths(&mut self, paths: Vec<&PathBuf>) {
self.layouts.retain(|path, _| paths.contains(&path));

for path_buf in paths {
if self.layouts.contains_key(path_buf) {
continue;
}

if let Some(cached_layout) = CachedLayout::from_path(path_buf) {
self.layouts.insert(path_buf.to_owned(), cached_layout);
fn load_layout(path: &Path) -> Option<Widget> {
match filetype::parse_layout(path) {
Ok(widget) => Some(widget),
Err(err) => {
warn!("The layout by path {path:?} is not valid. Error: {err}");
None
}
}
}
}

impl<'a> FromIterator<&'a PathBuf> for CachedLayouts {
fn from_iter<T: IntoIterator<Item = &'a PathBuf>>(iter: T) -> Self {
let layouts = iter
.into_iter()
.filter_map(|path_buf| {
CachedLayout::from_path(path_buf).map(|cached_layout| (path_buf, cached_layout))
})
.fold(HashMap::new(), |mut acc, (path_buf, cached_layout)| {
acc.insert(path_buf.to_owned(), cached_layout);
acc
});
impl CacheUpdate for CachedLayout {
fn check_updates(&mut self) -> FileState {
self.watcher.check_updates()
}

Self { layouts }
fn update(&mut self) {
self.layout = self.watcher.get_watching_path().and_then(Self::load_layout)
}
}

pub(super) struct CachedLayout {
watcher: FilesWatcher,
layout: Option<Widget>,
}
impl<'a> TryFrom<&'a PathBuf> for CachedLayout {
type Error = CachedValueError;

impl CachedLayout {
fn from_path(path_buf: &PathBuf) -> Option<Self> {
fn try_from(path_buf: &'a PathBuf) -> Result<Self, Self::Error> {
let watcher = match FilesWatcher::init(vec![path_buf]) {
Ok(watcher) => watcher,
Err(err) => {
error!("Failed to init watcher for file {path_buf:?}. Error: {err}");
return None;
return Err(CachedValueError::FailedInitWatcher { source: err });
}
};

let layout = watcher
.get_watching_path()
.and_then(CachedLayout::load_layout);

Some(CachedLayout { watcher, layout })
}

pub(super) fn layout(&self) -> Option<&Widget> {
self.layout.as_ref()
}

fn check_updates(&mut self) -> FileState {
self.watcher.check_updates()
}

fn update(&mut self) {
self.layout = self.watcher.get_watching_path().and_then(Self::load_layout)
}

fn load_layout(path: &Path) -> Option<Widget> {
match filetype::parse_layout(path) {
Ok(widget) => Some(widget),
Err(err) => {
warn!("The layout by path {path:?} is not valid. Error: {err}");
None
}
}
Ok(CachedLayout { watcher, layout })
}
}
25 changes: 20 additions & 5 deletions crates/backend/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ impl Renderer {
pub(crate) fn run(&mut self) -> anyhow::Result<()> {
let mut notifications_to_create = vec![];
let mut notifications_to_close = vec![];
let mut partially_default_config = false;

debug!("Renderer: Running");
loop {
Expand Down Expand Up @@ -72,19 +73,33 @@ impl Renderer {

{
match self.config.check_updates() {
FileState::NotFound | FileState::Updated => {
self.config.update();
self.window_manager.update_by_config(&self.config)?;
FileState::Updated => {
partially_default_config = false;
self.update_config()?;
info!("Renderer: Detected changes of config files and updated")
}
FileState::NothingChanged => (),
FileState::NotFound if !partially_default_config => {
partially_default_config = true;
self.update_config()?;
info!("The main or imported configuration file is not found, reverting this part to default values.");
}
FileState::NotFound | FileState::NothingChanged => (),
};

self.window_manager.update_cache();
// if self.config.update_themes() || self.window_manager.update_cache() {
if self.window_manager.update_cache() {
self.window_manager.update_by_config(&self.config)?;
}
}

std::thread::sleep(Duration::from_millis(50));
std::hint::spin_loop();
}
}

fn update_config(&mut self) -> anyhow::Result<()> {
self.config.update();
self.window_manager.update_by_config(&self.config)?;
Ok(())
}
}
32 changes: 17 additions & 15 deletions crates/backend/src/window.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use indexmap::{indexmap, IndexMap};
use log::{debug, error, trace};
use shared::cached_data::CachedData;
use std::{
cmp::Ordering,
fs::File,
os::{
fd::{AsFd, BorrowedFd},
unix::fs::FileExt,
},
path::PathBuf,
sync::Arc,
};

Expand All @@ -28,7 +30,7 @@ use super::internal_messages::RendererMessage;
use config::{self, Config};
use dbus::notification::{self, Notification};

use crate::{banner::BannerRect, cache::CachedLayouts};
use crate::{banner::BannerRect, cache::CachedLayout};
use render::{font::FontCollection, types::RectSize};

pub(super) struct Window {
Expand Down Expand Up @@ -154,20 +156,20 @@ impl Window {
debug!("Window: Reconfigured by updated config");
}

fn relocate(&mut self, (x, y): (u8, u8), anchor_cfg: &config::Anchor) {
fn relocate(&mut self, (x, y): (u8, u8), anchor_cfg: &config::general::Anchor) {
if let Some(layer_surface) = self.layer_surface.as_ref() {
debug!("Window: Relocate to anchor {anchor_cfg:?} with offsets x - {x} and y - {y}");
self.margin = Margin::from_anchor(x as i32, y as i32, anchor_cfg);

let anchor = match anchor_cfg {
config::Anchor::Top => Anchor::Top,
config::Anchor::TopLeft => Anchor::Top.union(Anchor::Left),
config::Anchor::TopRight => Anchor::Top.union(Anchor::Right),
config::Anchor::Bottom => Anchor::Bottom,
config::Anchor::BottomLeft => Anchor::Bottom.union(Anchor::Left),
config::Anchor::BottomRight => Anchor::Bottom.union(Anchor::Right),
config::Anchor::Left => Anchor::Left,
config::Anchor::Right => Anchor::Right,
config::general::Anchor::Top => Anchor::Top,
config::general::Anchor::TopLeft => Anchor::Top.union(Anchor::Left),
config::general::Anchor::TopRight => Anchor::Top.union(Anchor::Right),
config::general::Anchor::Bottom => Anchor::Bottom,
config::general::Anchor::BottomLeft => Anchor::Bottom.union(Anchor::Left),
config::general::Anchor::BottomRight => Anchor::Bottom.union(Anchor::Right),
config::general::Anchor::Left => Anchor::Left,
config::general::Anchor::Right => Anchor::Right,
};

layer_surface.set_anchor(anchor);
Expand All @@ -187,7 +189,7 @@ impl Window {
&mut self,
mut notifications: Vec<Notification>,
config: &Config,
cached_layouts: &CachedLayouts,
cached_layouts: &CachedData<PathBuf, CachedLayout>,
) {
self.replace_by_indices(&mut notifications, config, cached_layouts);

Expand All @@ -209,7 +211,7 @@ impl Window {
&mut self,
notifications: &mut Vec<Notification>,
config: &Config,
cached_layouts: &CachedLayouts,
cached_layouts: &CachedData<PathBuf, CachedLayout>,
) {
let matching_indices: Vec<usize> = notifications
.iter()
Expand Down Expand Up @@ -347,7 +349,7 @@ impl Window {
&mut self,
qhandle: &QueueHandle<Window>,
config: &Config,
cached_layouts: &CachedLayouts,
cached_layouts: &CachedData<PathBuf, CachedLayout>,
) {
self.banners
.values_mut()
Expand Down Expand Up @@ -392,7 +394,7 @@ impl Window {
vec![0; gap_size]
}

fn write_banners_to_buffer(&mut self, anchor: &config::Anchor, gap_buffer: &[u8]) {
fn write_banners_to_buffer(&mut self, anchor: &config::general::Anchor, gap_buffer: &[u8]) {
fn write(buffer: Option<&mut Buffer>, data: &[u8]) {
unsafe { buffer.unwrap_unchecked() }.push(data);
}
Expand Down Expand Up @@ -562,7 +564,7 @@ impl Margin {
}
}

fn from_anchor(x: i32, y: i32, anchor: &config::Anchor) -> Self {
fn from_anchor(x: i32, y: i32, anchor: &config::general::Anchor) -> Self {
let mut margin = Margin::new();

if anchor.is_top() {
Expand Down
Loading

0 comments on commit 9fbe317

Please sign in to comment.