Skip to content

Commit c4491b9

Browse files
committed
iter 2
1 parent 4f850d7 commit c4491b9

2 files changed

Lines changed: 230 additions & 0 deletions

File tree

examples/CLAUDE.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<claude-mem-context>
2+
# Recent Activity
3+
4+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5+
6+
### Jan 18, 2026
7+
8+
| ID | Time | T | Title | Read |
9+
|----|------|---|-------|------|
10+
| #107 | 2:19 PM | 🔄 | Changed Click Handler to Mouse Up Event | ~229 |
11+
| #106 | " | 🔄 | Refactored Floating Panel Button Design | ~362 |
12+
| #105 | " | 🔵 | Examined Hover Event Pattern | ~285 |
13+
| #104 | 2:17 PM | 🟣 | Implemented Floating Panel Example | ~439 |
14+
| #102 | 2:16 PM | 🔵 | Examined Advanced Window Customization Example | ~376 |
15+
| #101 | 2:15 PM | 🔵 | Examined SideBar Component with Router Integration | ~333 |
16+
| #94 | 2:14 PM | 🔵 | Examined FloatingTab Component with Router Integration | ~300 |
17+
| #93 | " | 🔵 | Examined Theme Preference Integration | ~287 |
18+
| #92 | " | 🔵 | Examined Transparent Window Configuration | ~280 |
19+
| #91 | " | 🔵 | Examined Multi-Window Feature Implementation | ~327 |
20+
</claude-mem-context>

examples/vertical_dock.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
#![cfg_attr(
2+
all(not(debug_assertions), target_os = "windows"),
3+
windows_subsystem = "windows"
4+
)]
5+
6+
use std::env;
7+
use std::path::PathBuf;
8+
use std::process::Command;
9+
10+
use freya::{
11+
prelude::*,
12+
winit::{dpi::LogicalPosition, window::WindowLevel},
13+
};
14+
15+
fn main() {
16+
let (width, height) = (64, 800);
17+
launch(
18+
LaunchConfig::new().with_window(
19+
WindowConfig::new(app)
20+
.with_title("Dock")
21+
.with_size(width as f64, height as f64)
22+
.with_decorations(false)
23+
.with_resizable(false)
24+
.with_background(Color::from_rgb(30, 30, 30))
25+
.with_window_attributes(move |attributes, el| {
26+
let attributes = attributes.with_window_level(WindowLevel::AlwaysOnTop);
27+
28+
// Position on left side of screen, centered vertically
29+
if let Some(monitor) = el
30+
.primary_monitor()
31+
.or_else(|| el.available_monitors().next())
32+
{
33+
let size = monitor.size();
34+
attributes.with_position(LogicalPosition {
35+
x: 0,
36+
y: size.height as i32 / 2 - height / 2,
37+
})
38+
} else {
39+
attributes
40+
}
41+
}),
42+
),
43+
)
44+
}
45+
46+
fn app() -> impl IntoElement {
47+
let apps = use_hook(get_pinned_apps);
48+
49+
rect()
50+
.width(Size::fill())
51+
.height(Size::fill())
52+
.padding(Gaps::new_all(8.))
53+
.spacing(8.)
54+
.background(Color::from_rgb(30, 30, 30))
55+
.corner_radius(8.)
56+
.children(apps.iter().filter_map(|app| {
57+
app.icon_path.as_ref().map(|icon_path| {
58+
dock_icon(icon_path.clone(), app.name.clone())
59+
})
60+
}))
61+
}
62+
63+
fn dock_icon(icon_path: PathBuf, name: String) -> Element {
64+
let mut hovered = use_state(|| false);
65+
66+
let background = if *hovered.read() {
67+
Color::from_rgb(60, 60, 60)
68+
} else {
69+
Color::TRANSPARENT
70+
};
71+
72+
rect()
73+
.center()
74+
.padding(Gaps::new_all(4.))
75+
.corner_radius(8.)
76+
.background(background)
77+
.on_pointer_enter(move |_| hovered.set(true))
78+
.on_pointer_leave(move |_| hovered.set(false))
79+
.on_mouse_up(move |_| {
80+
println!("Clicked: {}", name);
81+
})
82+
.child(
83+
ImageViewer::new(icon_path)
84+
.width(Size::px(40.))
85+
.height(Size::px(40.)),
86+
)
87+
.into()
88+
}
89+
90+
#[derive(Clone, Debug)]
91+
struct AppInfo {
92+
name: String,
93+
icon_path: Option<PathBuf>,
94+
}
95+
96+
fn get_pinned_apps() -> Vec<AppInfo> {
97+
let output = Command::new("dconf")
98+
.args(["read", "/org/gnome/shell/favorite-apps"])
99+
.output()
100+
.ok();
101+
102+
let Some(output) = output else {
103+
return Vec::new();
104+
};
105+
106+
let stdout = String::from_utf8_lossy(&output.stdout);
107+
108+
// Parse the GVariant array format: ['app1.desktop', 'app2.desktop', ...]
109+
let apps: Vec<String> = stdout
110+
.trim()
111+
.trim_start_matches('[')
112+
.trim_end_matches(']')
113+
.split(", ")
114+
.filter_map(|s| {
115+
let trimmed = s.trim().trim_matches('\'');
116+
if trimmed.is_empty() {
117+
None
118+
} else {
119+
Some(trimmed.to_string())
120+
}
121+
})
122+
.collect();
123+
124+
apps.into_iter()
125+
.map(|desktop_id| {
126+
let (name, icon_name) = parse_desktop_file(&desktop_id);
127+
let icon_path = icon_name.and_then(|icon| find_icon_path(&icon));
128+
AppInfo { name, icon_path }
129+
})
130+
.collect()
131+
}
132+
133+
fn parse_desktop_file(desktop_id: &str) -> (String, Option<String>) {
134+
let home = env::var("HOME").ok().map(PathBuf::from);
135+
136+
let search_paths = [
137+
Some(PathBuf::from("/usr/share/applications")),
138+
Some(PathBuf::from("/var/lib/flatpak/exports/share/applications")),
139+
home.as_ref().map(|h| h.join(".local/share/applications")),
140+
home.as_ref().map(|h| h.join(".local/share/flatpak/exports/share/applications")),
141+
Some(PathBuf::from("/var/lib/snapd/desktop/applications")),
142+
];
143+
144+
for path in search_paths.into_iter().flatten() {
145+
let desktop_path = path.join(desktop_id);
146+
if desktop_path.exists() {
147+
if let Ok(content) = std::fs::read_to_string(&desktop_path) {
148+
let mut name = desktop_id.trim_end_matches(".desktop").to_string();
149+
let mut icon = None;
150+
151+
for line in content.lines() {
152+
if line.starts_with("Name=") && !line.contains('[') {
153+
name = line.trim_start_matches("Name=").to_string();
154+
} else if line.starts_with("Icon=") {
155+
icon = Some(line.trim_start_matches("Icon=").to_string());
156+
}
157+
}
158+
159+
return (name, icon);
160+
}
161+
}
162+
}
163+
164+
(desktop_id.trim_end_matches(".desktop").to_string(), None)
165+
}
166+
167+
fn find_icon_path(icon_name: &str) -> Option<PathBuf> {
168+
// If it's already an absolute path, use it directly
169+
if icon_name.starts_with('/') {
170+
let path = PathBuf::from(icon_name);
171+
if path.exists() {
172+
return Some(path);
173+
}
174+
}
175+
176+
let home = env::var("HOME").ok().map(PathBuf::from);
177+
178+
let icon_dirs = [
179+
Some(PathBuf::from("/usr/share/icons/hicolor")),
180+
Some(PathBuf::from("/var/lib/flatpak/exports/share/icons/hicolor")),
181+
home.as_ref().map(|h| h.join(".local/share/icons/hicolor")),
182+
home.as_ref().map(|h| h.join(".local/share/flatpak/exports/share/icons/hicolor")),
183+
Some(PathBuf::from("/usr/share/pixmaps")),
184+
];
185+
186+
// Prefer larger icons
187+
let sizes = ["256x256", "128x128", "96x96", "64x64", "48x48", "scalable"];
188+
let extensions = ["png", "svg"];
189+
190+
for dir in icon_dirs.into_iter().flatten() {
191+
for size in &sizes {
192+
for ext in &extensions {
193+
let icon_path = dir.join(size).join("apps").join(format!("{}.{}", icon_name, ext));
194+
if icon_path.exists() {
195+
return Some(icon_path);
196+
}
197+
}
198+
}
199+
}
200+
201+
// Check pixmaps directory (flat structure)
202+
for ext in &extensions {
203+
let pixmap_path = PathBuf::from("/usr/share/pixmaps").join(format!("{}.{}", icon_name, ext));
204+
if pixmap_path.exists() {
205+
return Some(pixmap_path);
206+
}
207+
}
208+
209+
None
210+
}

0 commit comments

Comments
 (0)