Skip to content

Commit

Permalink
Add Bottom Panel Example (#46)
Browse files Browse the repository at this point in the history
* Implement bottom panel

* Remove thread sleep

* Remove unused iced features

* RustFmt

* Remove unwrap from the main method

* Add bottom panel image

* Update readme with bottom panel example

* Handle result

* Handle panel run result
  • Loading branch information
sundaram123krishnan authored Aug 27, 2024
1 parent 32888f0 commit 80e9861
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ iced binding for layershell

![example](./misc/iced_layershell_example.png)

![Bottom Panel Example](./misc/bottom_panel.png)

With this crate, you can use iced to build your kde-shell, notification application, and etc.

### iced-sessionlock
Expand Down
21 changes: 21 additions & 0 deletions iced_examples/bottom_panel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "bottom_panel"
authors.workspace = true
edition.workspace = true
version.workspace = true
license.workspace = true
repository.workspace = true
description.workspace = true
keywords.workspace = true
readme.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
iced = { version = "0.12.1", features = ["image", "svg"]}
iced_runtime.workspace = true
iced_layershell.workspace = true
gio = "0.20.0"
regex = "1.10.5"
xdg = "2.5.2"
tracing = "0.1.40"
3 changes: 3 additions & 0 deletions iced_examples/bottom_panel/misc/text-plain.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions iced_examples/bottom_panel/src/applications.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use std::path::PathBuf;
use std::str::FromStr;

use gio::{AppLaunchContext, DesktopAppInfo};

use crate::Message;
use gio::prelude::*;
use iced::widget::{button, image, row, svg};
use iced::{theme, Element, Length};

static DEFAULT_ICON: &[u8] = include_bytes!("../misc/text-plain.svg");

#[derive(Debug, Clone)]
pub struct App {
app_info: DesktopAppInfo,
icon: Option<PathBuf>,
}

impl App {
pub fn launch(&self) {
self.app_info.launch(&[], AppLaunchContext::NONE).unwrap()
}

fn icon(&self) -> Element<Message> {
match &self.icon {
Some(path) => {
if path
.as_os_str()
.to_str()
.is_some_and(|pathname| pathname.ends_with("png"))
{
image(image::Handle::from_path(path))
.width(Length::Fixed(80.))
.height(Length::Fixed(80.))
.into()
} else {
svg(svg::Handle::from_path(path))
.width(Length::Fixed(40.))
.height(Length::Fixed(40.))
.into()
}
}
None => svg(svg::Handle::from_memory(DEFAULT_ICON))
.width(Length::Fixed(40.))
.height(Length::Fixed(40.))
.into(),
}
}

pub fn view(&self, index: usize, selected: bool) -> Element<Message> {
button(row![self.icon(),].spacing(10))
.on_press(Message::Launch(index))
.width(Length::Fill)
.height(Length::Fill)
.style(if selected {
theme::Button::Primary
} else {
theme::Button::Secondary
})
.into()
}
}

static ICONS_SIZE: &[&str] = &["256x256", "256x256"];

static THEMES_LIST: &[&str] = &["yaru"];

fn get_icon_path_from_xdgicon(iconname: &str) -> Option<PathBuf> {
let scalable_icon_path =
xdg::BaseDirectories::with_prefix("icons/hicolor/scalable/apps").unwrap();
if let Some(iconpath) = scalable_icon_path.find_data_file(format!("{iconname}.svg")) {
return Some(iconpath);
}
for prefix in ICONS_SIZE {
let iconpath =
xdg::BaseDirectories::with_prefix(&format!("icons/hicolor/{prefix}/apps")).unwrap();
if let Some(iconpath) = iconpath.find_data_file(format!("{iconname}.png")) {
return Some(iconpath);
}
}
let pixmappath = xdg::BaseDirectories::with_prefix("pixmaps").unwrap();
if let Some(iconpath) = pixmappath.find_data_file(format!("{iconname}.svg")) {
return Some(iconpath);
}
if let Some(iconpath) = pixmappath.find_data_file(format!("{iconname}.png")) {
return Some(iconpath);
}
for themes in THEMES_LIST {
let iconpath =
xdg::BaseDirectories::with_prefix(&format!("icons/{themes}/apps/48")).unwrap();
if let Some(iconpath) = iconpath.find_data_file(format!("{iconname}.svg")) {
return Some(iconpath);
}
let iconpath =
xdg::BaseDirectories::with_prefix(&format!("icons/{themes}/apps/64")).unwrap();
if let Some(iconpath) = iconpath.find_data_file(format!("{iconname}.svg")) {
return Some(iconpath);
}
}
None
}

fn get_icon_path(iconname: &str) -> Option<PathBuf> {
if iconname.contains('/') {
PathBuf::from_str(iconname).ok()
} else {
get_icon_path_from_xdgicon(iconname)
}
}
pub fn all_apps() -> Vec<App> {
gio::AppInfo::all()
.iter()
.filter(|app| app.should_show() && app.downcast_ref::<DesktopAppInfo>().is_some())
.map(|app| app.clone().downcast::<DesktopAppInfo>().unwrap())
.take(10)
.map(|app| App {
app_info: app.clone(),
icon: match &app.icon() {
None => None,
Some(icon) => {
let iconname = gio::prelude::IconExt::to_string(icon).unwrap();
get_icon_path(iconname.as_str())
}
},
})
.collect()
}
64 changes: 64 additions & 0 deletions iced_examples/bottom_panel/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
mod applications;
use applications::{all_apps, App};
use iced::widget::row;
use iced::{Command, Element, Theme};

use iced_layershell::reexport::{Anchor, Layer};
use iced_layershell::settings::{LayerShellSettings, Settings};
use iced_layershell::Application;

fn main() -> Result<(), iced_layershell::Error> {
Panel::run(Settings {
layer_settings: LayerShellSettings {
size: Some((600, 50)),
anchor: Anchor::Bottom,
layer: Layer::Overlay,
margin: (0, 0, 10, 0),
..Default::default()
},
..Default::default()
})
}

struct Panel {
apps: Vec<App>,
}

#[derive(Debug, Clone)]
enum Message {
Launch(usize),
}

impl Application for Panel {
type Executor = iced::executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();

fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(Self { apps: all_apps() }, Command::none())
}
fn namespace(&self) -> String {
String::from("bottom panel")
}

fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::Launch(index) => {
self.apps[index].launch();
Command::none()
}
}
}

fn view(&self) -> Element<Message> {
let bottom_vec: Vec<Element<Message>> = self
.apps
.iter()
.enumerate()
.map(|(index, app)| app.view(index, false))
.collect();

row(bottom_vec).into()
}
}
Binary file added misc/bottom_panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 80e9861

Please sign in to comment.