Skip to content

Commit 7e6281f

Browse files
committed
Add support for images + better styling
1 parent c7c69d5 commit 7e6281f

File tree

4 files changed

+109
-13
lines changed

4 files changed

+109
-13
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ edition = "2024"
77
anyhow = "1.0.100"
88
applications = "0.3.1"
99
global-hotkey = "0.7.0"
10-
iced = { version = "0.14.0", features = ["smol", "tokio"] }
10+
iced = { version = "0.14.0", features = ["image", "smol", "tokio"] }
1111
icns = "0.3.1"
12+
image = "0.25.9"
1213
objc2 = "0.6.3"
1314
objc2-app-kit = "0.3.2"
1415
objc2-foundation = "0.3.2"

src/app.rs

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::macos;
22
use crate::macos::focus_this_app;
33

44
use global_hotkey::{GlobalHotKeyEvent, HotKeyState};
5+
use iced::Alignment;
56
use iced::Fill;
67
use iced::alignment::Vertical;
78
use iced::futures;
@@ -11,10 +12,16 @@ use iced::stream;
1112
use iced::widget::Button;
1213
use iced::widget::Row;
1314
use iced::widget::Text;
15+
use iced::widget::container;
16+
use iced::widget::image::Handle;
17+
use iced::widget::image::Viewer;
18+
use iced::widget::scrollable;
1419
use iced::widget::text::LineHeight;
1520
use iced::widget::{Column, operation, space, text_input};
1621
use iced::window::{self, Id, Settings};
1722
use iced::{Element, Subscription, Task, Theme};
23+
use icns::IconFamily;
24+
use image::RgbaImage;
1825
use objc2::rc::Retained;
1926
use objc2_app_kit::NSRunningApplication;
2027

@@ -43,6 +50,25 @@ fn log_error_and_exit(msg: &str) {
4350
exit(-1)
4451
}
4552

53+
fn handle_from_icns(path: &Path) -> Option<Handle> {
54+
let data = std::fs::read(path).ok()?;
55+
let family = IconFamily::read(std::io::Cursor::new(&data)).ok()?;
56+
57+
let icon_type = family.available_icons();
58+
59+
let icon = family.get_icon_with_type(*icon_type.get(0)?).ok()?;
60+
let image = RgbaImage::from_raw(
61+
icon.width() as u32,
62+
icon.height() as u32,
63+
icon.data().to_vec(),
64+
)?;
65+
Some(Handle::from_rgba(
66+
image.width(),
67+
image.height(),
68+
image.into_raw(),
69+
))
70+
}
71+
4672
pub fn get_installed_apps(dir: impl AsRef<Path>) -> Vec<App> {
4773
fs::read_dir(dir)
4874
.unwrap_or_else(|x| {
@@ -75,11 +101,38 @@ pub fn get_installed_apps(dir: impl AsRef<Path>) -> Vec<App> {
75101
exit(-1)
76102
});
77103

104+
let direntry = fs::read_dir(format!("{}/Contents/Resources", path_str))
105+
.into_iter()
106+
.flatten()
107+
.filter_map(|x| {
108+
let file = x.ok()?;
109+
let name = file.file_name();
110+
let file_name = name.to_str()?;
111+
if file_name.ends_with(".icns") {
112+
Some(file.path())
113+
} else {
114+
None
115+
}
116+
})
117+
.collect::<Vec<PathBuf>>();
118+
119+
let icons = if direntry.len() > 1 {
120+
let icns_vec = direntry
121+
.iter()
122+
.filter(|x| x.ends_with("AppIcon.icns"))
123+
.collect::<Vec<&PathBuf>>();
124+
handle_from_icns(icns_vec.first().unwrap_or(&&PathBuf::new()))
125+
} else if direntry.len() > 0 {
126+
handle_from_icns(direntry.first().unwrap_or(&PathBuf::new()))
127+
} else {
128+
None
129+
};
130+
78131
let name = file_name.strip_suffix(".app").unwrap().to_string();
79132

80133
Some(App {
81134
open_command: format!("open {}", path_str),
82-
icon_path: None,
135+
icons,
83136
name_lc: name.to_lowercase(),
84137
name,
85138
})
@@ -91,7 +144,7 @@ pub fn get_installed_apps(dir: impl AsRef<Path>) -> Vec<App> {
91144
#[derive(Debug, Clone)]
92145
pub struct App {
93146
open_command: String,
94-
icon_path: Option<PathBuf>,
147+
icons: Option<iced::widget::image::Handle>,
95148
name: String,
96149
name_lc: String,
97150
}
@@ -100,6 +153,18 @@ impl App {
100153
pub fn render(&self) -> impl Into<iced::Element<'_, Message>> {
101154
let mut tile = Row::new().width(Fill).height(55);
102155

156+
if let Some(icon) = &self.icons {
157+
tile = tile
158+
.push(Viewer::new(icon).height(35).width(35))
159+
.align_y(Alignment::Center);
160+
} else {
161+
tile = tile
162+
.push(space().height(Fill))
163+
.width(55)
164+
.height(55)
165+
.align_y(Alignment::Center);
166+
}
167+
103168
tile = tile.push(
104169
Button::new(
105170
Text::new(self.name.clone())
@@ -108,11 +173,20 @@ impl App {
108173
.align_y(Vertical::Center),
109174
)
110175
.on_press(Message::RunShellCommand(self.open_command.clone()))
111-
.width(Fill)
112-
.height(Fill),
113-
);
114-
115-
tile
176+
.style(|_, _| iced::widget::button::Style {
177+
background: Some(iced::Background::Color(
178+
Theme::KanagawaDragon.palette().background,
179+
)),
180+
text_color: Theme::KanagawaDragon.palette().text,
181+
..Default::default()
182+
})
183+
.width(Fill).height(55)
184+
).width(Fill);
185+
container(tile).style(|_| iced::widget::container::Style {
186+
text_color: Some(Theme::KanagawaDragon.palette().text),
187+
background: Some(iced::Background::Color(Theme::KanagawaDragon.palette().background)),
188+
..Default::default()
189+
}).width(Fill).height(Fill)
116190
}
117191
}
118192

@@ -175,7 +249,7 @@ pub struct Tile {
175249
impl Tile {
176250
/// A base window
177251
pub fn new() -> (Self, Task<Message>) {
178-
// let _ = create_tray_icons();
252+
// let _ = create_tray_icons();
179253
let (id, open) = window::open(default_settings());
180254
let _ = window::run(id, |handle| {
181255
macos::macos_window_config(
@@ -229,7 +303,7 @@ impl Tile {
229303
} else if self.query_lc == "randomvar" {
230304
self.results = vec![App {
231305
open_command: "".to_string(),
232-
icon_path: None,
306+
icons: None,
233307
name: rand::random_range(0..100).to_string(),
234308
name_lc: String::new(),
235309
}];
@@ -240,7 +314,6 @@ impl Tile {
240314
height: DEFAULT_WINDOW_HEIGHT + 55.,
241315
},
242316
);
243-
244317
}
245318

246319
let filter_vec = if self.query_lc.starts_with(&self.prev_query_lc) {
@@ -355,7 +428,10 @@ impl Tile {
355428
search_results = search_results.push((result).render());
356429
}
357430

358-
Column::new().push(title_input).push(search_results).into()
431+
Column::new()
432+
.push(title_input)
433+
.push(scrollable(search_results))
434+
.into()
359435
} else {
360436
space().into()
361437
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn main() -> iced::Result {
2020
.register_all(&[altspace])
2121
.expect("Unable to register hotkey");
2222

23-
iced::daemon(Tile::new, Tile::update, crate::app::view)
23+
iced::daemon(Tile::new, Tile::update, Tile::view)
2424
.subscription(Tile::subscription)
2525
.theme(Tile::theme)
2626
.run()

0 commit comments

Comments
 (0)