Skip to content

Commit

Permalink
Merge pull request #10 from l4l/icons
Browse files Browse the repository at this point in the history
  • Loading branch information
l4l authored Dec 9, 2020
2 parents 6759126 + 4020f98 commit ebc0aef
Show file tree
Hide file tree
Showing 11 changed files with 713 additions and 17 deletions.
414 changes: 413 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ serde = { version = "1.0.117", features = ["derive"] }
toml = "0.5.7"
structopt = "0.3.21"
either = "1.6.1"
lazy_static = "1.4.0"
png = "0.16.7"
resvg = "0.12.0"
usvg = "0.12.0"
once_cell = "1.5.2"

[profile.release]
lto = true
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ bg_color = 0x75715eff
# font = ...
font_color = 0xf8f8f2ff
selected_font_color = 0xa6e22eff

# When section presents, icons are displayed
[icon]
size = 16 # no scaling is performed, so need to choose exact size
theme = "Adwaita"
# if no icon found for an app, this on will be used instead
fallback_icon_path = "/usr/share/icons/Adwaita/16x16/categories/applications-engineering-symbolic.symbolic.png"
```

## Running
Expand Down
9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct Config {
bg_color: Option<u32>,
font_color: Option<u32>,

icon: Option<Icon>,

input_text: Option<InputText>,
list_items: Option<ListItems>,
}
Expand All @@ -35,6 +37,13 @@ struct ListItems {
selected_font_color: Option<u32>,
}

#[derive(Serialize, Deserialize)]
struct Icon {
size: Option<u32>,
theme: Option<String>,
fallback_icon_path: Option<PathBuf>,
}

fn config_path() -> PathBuf {
xdg::BaseDirectories::with_prefix(crate::prog_name!())
.unwrap()
Expand Down
25 changes: 25 additions & 0 deletions src/config/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use font_kit::source::SystemSource;
use raqote::SolidSource;

use super::Config;
use crate::desktop::{IconConfig, DEFAULT_ICON_SIZE, DEFAULT_THEME};
use crate::draw::{BgParams, InputTextParams, ListParams};
use crate::surface::Params as SurfaceParams;

Expand Down Expand Up @@ -59,6 +60,17 @@ impl<'a> From<&'a Config> for ListParams {
.and_then(|c| c.selected_font_color)
.map(u32_to_solid_source)
.unwrap_or_else(|| SolidSource::from_unpremultiplied_argb(0xff, 0xa6, 0xe2, 0x2e)),
icon_size: config
.icon
.as_ref()
.map(|i| i.size.unwrap_or(DEFAULT_ICON_SIZE)),
fallback_icon: config
.icon
.as_ref()
.and_then(|i| i.fallback_icon_path.as_ref())
.map(|path| {
crate::icon::Icon::load_icon(&path).expect("cannot load fallback icon")
}),
}
}
}
Expand All @@ -84,6 +96,19 @@ impl<'a> From<&'a Config> for SurfaceParams {
}
}

impl<'a> From<&'a Config> for Option<IconConfig> {
fn from(config: &'a Config) -> Option<IconConfig> {
config.icon.as_ref().map(|c| IconConfig {
icon_size: c.size.unwrap_or(DEFAULT_ICON_SIZE),
theme: c
.theme
.as_ref()
.unwrap_or_else(|| once_cell::sync::Lazy::force(&DEFAULT_THEME))
.clone(),
})
}
}

std::thread_local! {
static FONT: Font = SystemSource::new()
.select_best_match(&[FamilyName::SansSerif], &Properties::new())
Expand Down
121 changes: 115 additions & 6 deletions src/desktop.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
use std::fs::{self, DirEntry};
use std::path::{Path, PathBuf};

use lazy_static::lazy_static;
use once_cell::sync::{Lazy, OnceCell};
use xdg::BaseDirectories;

lazy_static! {
pub static ref XDG_DIRS: BaseDirectories = BaseDirectories::new().unwrap();
}
use crate::icon::Icon;

pub static XDG_DIRS: OnceCell<BaseDirectories> = OnceCell::new(); //BaseDirectories::new().unwrap();

pub struct Entry {
pub name: String,
pub desktop_fname: String,
pub exec: String,
pub is_terminal: bool,
pub icon: Option<Icon>,
}

pub fn xdg_dirs<'a>() -> &'a BaseDirectories {
XDG_DIRS.get_or_init(|| BaseDirectories::new().unwrap())
}

pub fn find_entries() -> Vec<Entry> {
let mut dirs = XDG_DIRS.get_data_dirs();
dirs.push(XDG_DIRS.get_data_home());
let xdg_dirs = xdg_dirs();

let mut dirs = xdg_dirs.get_data_dirs();
dirs.push(xdg_dirs.get_data_home());
let mut entries = vec![];
traverse_dirs(&mut entries, dirs);
entries.sort_unstable_by(|x, y| x.name.cmp(&y.name));
Expand All @@ -42,6 +49,9 @@ fn read_dir(path: &Path) -> impl Iterator<Item = DirEntry> {
fn traverse_dirs(mut entries: &mut Vec<Entry>, paths: impl IntoIterator<Item = PathBuf>) {
for path in paths.into_iter() {
let apps_dir = path.join("applications");
if !apps_dir.exists() {
continue;
}

for dir_entry in read_dir(&apps_dir) {
traverse_dir_entry(&mut entries, dir_entry);
Expand Down Expand Up @@ -87,6 +97,11 @@ fn traverse_dir_entry(mut entries: &mut Vec<Entry>, dir_entry: DirEntry) {
.attr("Terminal")
.map(|s| s == "true")
.unwrap_or(false),
icon: main_section.attr("Icon").and_then(|name| {
icon_paths()
.and_then(|p| p.get(name))
.and_then(|icons| icons.iter().filter_map(Icon::load_icon).next())
}),
});
}
(n, e) => {
Expand All @@ -99,3 +114,97 @@ fn traverse_dir_entry(mut entries: &mut Vec<Entry>, dir_entry: DirEntry) {
}
}
}

const FALLBACK_THEME: &str = "hicolor";
pub const DEFAULT_ICON_SIZE: u32 = 16;

pub static DEFAULT_THEME: Lazy<String> = Lazy::new(|| {
let path = PathBuf::from("/usr/share/icons/default/index.theme");

fep::parse_entry(path)
.map_err(|e| log::error!("failed to parse default entry: {}", e))
.ok()
.and_then(|entry| {
entry
.section("Icon Theme")
.attr("Inherits")
.map(|s| s.to_string())
})
.unwrap_or_else(|| FALLBACK_THEME.to_string())
});

pub struct IconConfig {
pub icon_size: u32,
pub theme: String,
}

use std::collections::HashMap;

type IconPaths = HashMap<String, Vec<PathBuf>>;
static ICON_PATHS: OnceCell<IconPaths> = OnceCell::new();

pub fn find_icon_paths(config: IconConfig) -> Result<(), ()> {
ICON_PATHS.set(traverse_icon_dirs(config)).map_err(|_| ())
}

pub fn icon_paths<'a>() -> Option<&'a IconPaths> {
ICON_PATHS.get()
}

fn traverse_icon_dirs(config: IconConfig) -> IconPaths {
let mut icons = IconPaths::new();

fn traverse_dir(mut icons: &mut IconPaths, theme: &str, icon_size: u32) {
for dir in xdg_dirs().get_data_dirs() {
let base_path = dir
.join("icons")
.join(&theme)
.join(format!("{0}x{0}", icon_size));

if !base_path.exists() {
continue;
}

for entry in read_dir(&base_path) {
traverse_icon_dir(&mut icons, entry);
}
}
}

traverse_dir(&mut icons, &config.theme, config.icon_size);
if config.theme != FALLBACK_THEME {
traverse_dir(&mut icons, FALLBACK_THEME, config.icon_size);
}

icons
}

fn traverse_icon_dir(mut icons: &mut IconPaths, entry: DirEntry) {
let entry_path = entry.path();

match entry.file_type() {
Err(err) => log::warn!("failed to get `{:?}` file type: {}", entry_path, err),
Ok(tp) if tp.is_dir() => {
for entry in read_dir(&entry_path) {
traverse_icon_dir(&mut icons, entry);
}

return;
}
_ => {}
}

match entry_path.extension().and_then(|ext| ext.to_str()) {
Some("png") | Some("svg") => {
let icon_name = entry_path
.file_stem()
.and_then(|f| f.to_str())
.expect("failed to get icon name");
icons
.entry(icon_name.to_owned())
.or_default()
.push(entry_path);
}
_ => {}
}
}
26 changes: 23 additions & 3 deletions src/draw/list_view.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use std::marker::PhantomData;

use font_kit::loaders::freetype::Font;
use raqote::{DrawOptions, DrawTarget, Point, SolidSource, Source};
use raqote::{DrawOptions, DrawTarget, Image, Point, SolidSource, Source};

use super::{Drawable, Space};

const ENTRY_HEIGHT: f32 = 25.;
const ENTRY_HEIGHT: f32 = 28.;

pub struct Params {
pub font: Font,
pub font_color: SolidSource,
pub selected_font_color: SolidSource,
pub icon_size: Option<u32>,
pub fallback_icon: Option<crate::icon::Icon>,
}

pub struct ListItem<'a> {
pub name: &'a str,
pub icon: Option<Image<'a>>,
}

pub struct ListView<'a, It> {
Expand Down Expand Up @@ -47,7 +50,24 @@ where
if relative_offset + ENTRY_HEIGHT > space.height {
break;
}
let pos = Point::new(point.x + 10., top_offset + relative_offset);

let x_offset = point.x + 10.;
let y_offset = top_offset + relative_offset;

let fallback_icon = self.params.fallback_icon.as_ref().map(|i| i.as_image());
if let Some(icon) = item.icon.as_ref().or_else(|| fallback_icon.as_ref()) {
dt.draw_image_at(
x_offset,
y_offset - icon.height as f32,
&icon,
&DrawOptions::default(),
);
}

let pos = Point::new(
x_offset + self.params.icon_size.map(|s| s as f32 + 3.0).unwrap_or(0.0),
y_offset,
);
let color = if i + skip == self.selected_item {
self.params.selected_font_color
} else {
Expand Down
Loading

0 comments on commit ebc0aef

Please sign in to comment.