Skip to content

Commit fbe6f44

Browse files
committed
Add macOS-style dock and app launching system
Introduces a new Dock UI component with glassy effects and magnification, enabling app launching from the dock. Refactors desktop UI creation to use the Dock and Wallpaper components, adds an app launch queue for communication between dock and compositor, and implements window creation for several built-in apps. Updates window decorations for a more authentic macOS dark theme look with improved shadows and traffic light buttons.
1 parent 38b56da commit fbe6f44

4 files changed

Lines changed: 910 additions & 216 deletions

File tree

crates/prisma_compositor/src/compositor.rs

Lines changed: 208 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// Main compositor orchestrating the GPU-accelerated desktop environment
2-
use std::sync::Arc;
2+
use std::sync::{Arc, Mutex};
33
use std::time::{Duration, Instant};
4+
use std::collections::VecDeque;
45
use winit::{
56
dpi::PhysicalSize,
67
event_loop::EventLoop,
@@ -12,13 +13,25 @@ use crate::{
1213
core::Context,
1314
renderer::Renderer,
1415
window::{WindowManager, WindowId},
15-
ui::{UITree, Container, Button, Text, Rectangle, LayoutDirection, UIElement, LayoutConstraints},
16+
ui::{UITree, Container, Button, Text, LayoutDirection, UIElement, LayoutConstraints, Dock, Wallpaper},
1617
events::{EventDispatcher, Event, InputEvent, MouseButton, ButtonState, Modifiers},
1718
text::TextRenderer,
1819
assets::AssetManager,
1920
geometry::{Rect, Size, Color},
2021
};
2122

23+
/// Global app launch queue for communication between dock and compositor
24+
static APP_LAUNCH_QUEUE: std::sync::LazyLock<Arc<Mutex<VecDeque<String>>>> = std::sync::LazyLock::new(|| {
25+
Arc::new(Mutex::new(VecDeque::new()))
26+
});
27+
28+
/// Queue an app to be launched
29+
pub fn queue_app_launch(app_id: String) {
30+
if let Ok(mut queue) = APP_LAUNCH_QUEUE.lock() {
31+
queue.push_back(app_id);
32+
}
33+
}
34+
2235
/// Configuration for the compositor
2336
#[derive(Debug, Clone)]
2437
pub struct CompositorConfig {
@@ -311,95 +324,24 @@ impl Compositor {
311324
Ok(())
312325
}
313326

314-
/// Create macOS-style desktop UI with dock and proper layout
327+
/// Create macOS-style desktop UI with beautiful glassy dock and wallpaper
315328
fn create_desktop_ui(&mut self) {
316-
// Create macOS-style dock at the bottom
317-
let mut dock = Container::new("dock".to_string())
318-
.with_direction(LayoutDirection::Horizontal);
319-
320-
// Dock constraints - centered, glass effect, proper macOS sizing
321-
dock.layout_mut().constraints = LayoutConstraints {
322-
preferred_size: Some(Size::new(400.0, 68.0)), // macOS dock height
323-
min_size: Size::new(300.0, 68.0),
324-
max_size: Size::new(600.0, 68.0),
325-
flex_grow: 0.0,
329+
// Create beautiful wallpaper with floating effects
330+
let mut wallpaper = Wallpaper::new("wallpaper".to_string());
331+
wallpaper.layout_mut().constraints = LayoutConstraints {
332+
preferred_size: Some(Size::new(self.context.size.width as f32, self.context.size.height as f32)),
333+
min_size: Size::new(0.0, 0.0),
334+
max_size: Size::new(f32::INFINITY, f32::INFINITY),
335+
flex_grow: 1.0,
326336
flex_shrink: 0.0,
327337
aspect_ratio: None,
328338
};
329339

330-
// macOS-style dock background (translucent glass effect)
331-
let mut dock_background = Rectangle::new("dock_background".to_string())
332-
.with_color(Color::new(0.1, 0.1, 0.1, 0.8)) // Dark translucent
333-
.with_corner_radius(16.0); // Rounded corners like macOS
340+
// Create beautiful glassy dock with default apps
341+
let mut dock = Dock::new("dock".to_string());
342+
dock.add_default_apps();
334343

335-
// Create dock app icons with proper macOS styling
336-
let mut finder_icon = Button::new("finder".to_string(), "📁".to_string())
337-
.with_colors(
338-
Color::new(0.2, 0.2, 0.2, 0.9), // Subtle background
339-
Color::new(0.3, 0.3, 0.3, 0.9), // Hover
340-
Color::new(0.4, 0.4, 0.4, 0.9), // Active
341-
);
342-
finder_icon.layout_mut().constraints = LayoutConstraints {
343-
preferred_size: Some(Size::new(52.0, 52.0)), // macOS dock icon size
344-
min_size: Size::new(48.0, 48.0),
345-
max_size: Size::new(64.0, 64.0),
346-
flex_grow: 0.0,
347-
flex_shrink: 0.0,
348-
aspect_ratio: Some(1.0), // Square icons
349-
};
350-
351-
let mut safari_icon = Button::new("safari".to_string(), "🌐".to_string())
352-
.with_colors(
353-
Color::new(0.2, 0.2, 0.2, 0.9),
354-
Color::new(0.3, 0.3, 0.3, 0.9),
355-
Color::new(0.4, 0.4, 0.4, 0.9),
356-
);
357-
safari_icon.layout_mut().constraints = LayoutConstraints {
358-
preferred_size: Some(Size::new(52.0, 52.0)),
359-
min_size: Size::new(48.0, 48.0),
360-
max_size: Size::new(64.0, 64.0),
361-
flex_grow: 0.0,
362-
flex_shrink: 0.0,
363-
aspect_ratio: Some(1.0),
364-
};
365-
366-
let mut terminal_icon = Button::new("terminal".to_string(), "⚫".to_string())
367-
.with_colors(
368-
Color::new(0.2, 0.2, 0.2, 0.9),
369-
Color::new(0.3, 0.3, 0.3, 0.9),
370-
Color::new(0.4, 0.4, 0.4, 0.9),
371-
);
372-
terminal_icon.layout_mut().constraints = LayoutConstraints {
373-
preferred_size: Some(Size::new(52.0, 52.0)),
374-
min_size: Size::new(48.0, 48.0),
375-
max_size: Size::new(64.0, 64.0),
376-
flex_grow: 0.0,
377-
flex_shrink: 0.0,
378-
aspect_ratio: Some(1.0),
379-
};
380-
381-
let mut settings_icon = Button::new("settings".to_string(), "⚙️".to_string())
382-
.with_colors(
383-
Color::new(0.2, 0.2, 0.2, 0.9),
384-
Color::new(0.3, 0.3, 0.3, 0.9),
385-
Color::new(0.4, 0.4, 0.4, 0.9),
386-
);
387-
settings_icon.layout_mut().constraints = LayoutConstraints {
388-
preferred_size: Some(Size::new(52.0, 52.0)),
389-
min_size: Size::new(48.0, 48.0),
390-
max_size: Size::new(64.0, 64.0),
391-
flex_grow: 0.0,
392-
flex_shrink: 0.0,
393-
aspect_ratio: Some(1.0),
394-
};
395-
396-
// Add icons to dock with proper spacing
397-
dock.add_child(Box::new(finder_icon));
398-
dock.add_child(Box::new(safari_icon));
399-
dock.add_child(Box::new(terminal_icon));
400-
dock.add_child(Box::new(settings_icon));
401-
402-
// Create desktop container with stack layout
344+
// Create desktop container with stack layout for proper layering
403345
let mut desktop = Container::new("desktop".to_string())
404346
.with_direction(LayoutDirection::Stack);
405347

@@ -412,22 +354,13 @@ impl Compositor {
412354
aspect_ratio: None,
413355
};
414356

415-
// macOS-style gradient wallpaper
416-
let mut wallpaper = Rectangle::new("wallpaper".to_string())
417-
.with_color(Color::from_hex("#4c1d95").unwrap_or(Color::new(0.3, 0.11, 0.58, 1.0))); // macOS purple gradient
418-
wallpaper.layout_mut().constraints = LayoutConstraints {
419-
preferred_size: Some(Size::new(self.context.size.width as f32, self.context.size.height as f32)),
420-
min_size: Size::new(0.0, 0.0),
421-
max_size: Size::new(f32::INFINITY, f32::INFINITY),
422-
flex_grow: 1.0,
423-
flex_shrink: 0.0,
424-
aspect_ratio: None,
425-
};
426-
357+
// Add wallpaper and dock - order matters for layering
427358
desktop.add_child(Box::new(wallpaper));
428359
desktop.add_child(Box::new(dock));
429360

430361
self.desktop_ui.set_root(Box::new(desktop));
362+
363+
println!("🖥️ Beautiful macOS-style desktop created with glassy dock!");
431364
}
432365

433366
/// Create a demo window with macOS-style design
@@ -455,7 +388,10 @@ impl Compositor {
455388
Color::new(0.0, 0.48, 1.0, 1.0), // macOS blue
456389
Color::new(0.0, 0.42, 0.9, 1.0), // Hover
457390
Color::new(0.0, 0.36, 0.8, 1.0), // Active
458-
);
391+
)
392+
.on_click(|| {
393+
println!("🚀 Demo: Launching Finder...");
394+
});
459395
button1.layout_mut().constraints = LayoutConstraints {
460396
preferred_size: Some(Size::new(140.0, 32.0)),
461397
min_size: Size::new(100.0, 28.0),
@@ -470,7 +406,10 @@ impl Compositor {
470406
Color::new(0.55, 0.55, 0.55, 1.0), // macOS gray
471407
Color::new(0.65, 0.65, 0.65, 1.0), // Hover
472408
Color::new(0.45, 0.45, 0.45, 1.0), // Active
473-
);
409+
)
410+
.on_click(|| {
411+
println!("🚀 Demo: Launching Terminal...");
412+
});
474413
button2.layout_mut().constraints = LayoutConstraints {
475414
preferred_size: Some(Size::new(140.0, 32.0)),
476415
min_size: Size::new(100.0, 28.0),
@@ -500,6 +439,173 @@ impl Compositor {
500439
self.demo_window_id = Some(window_id);
501440
}
502441

442+
/// Create a new app window based on app ID
443+
fn create_app_window(&mut self, app_id: &str) {
444+
let (title, content) = match app_id {
445+
"finder" => self.create_finder_window(),
446+
"terminal" => self.create_terminal_window(),
447+
"code" => self.create_code_editor_window(),
448+
"browser" => self.create_browser_window(),
449+
"calculator" => self.create_calculator_window(),
450+
"settings" => self.create_settings_window(),
451+
"music" => self.create_music_window(),
452+
"photos" => self.create_photos_window(),
453+
_ => ("Unknown App".to_string(), Box::new(Text::new("unknown".to_string(), "Unknown Application".to_string())) as Box<dyn UIElement>),
454+
};
455+
456+
let window_id = self.window_manager.create_window(title, content);
457+
println!("🚀 Launched {} (Window ID: {:?})", app_id, window_id);
458+
}
459+
460+
/// Create Finder file manager window
461+
fn create_finder_window(&self) -> (String, Box<dyn UIElement>) {
462+
let mut content = Container::new("finder_content".to_string())
463+
.with_direction(LayoutDirection::Vertical);
464+
465+
let title = Text::new("finder_title".to_string(), "Finder".to_string())
466+
.with_font_size(18.0)
467+
.with_color(Color::new(0.1, 0.1, 0.1, 1.0));
468+
469+
let file_list = Container::new("file_list".to_string())
470+
.with_direction(LayoutDirection::Vertical);
471+
472+
content.add_child(Box::new(title));
473+
content.add_child(Box::new(file_list));
474+
475+
("Finder".to_string(), Box::new(content))
476+
}
477+
478+
/// Create Terminal window
479+
fn create_terminal_window(&self) -> (String, Box<dyn UIElement>) {
480+
let mut content = Container::new("terminal_content".to_string())
481+
.with_direction(LayoutDirection::Vertical);
482+
483+
let prompt = Text::new("terminal_prompt".to_string(), "user@prisma:~$ ".to_string())
484+
.with_font_size(14.0)
485+
.with_color(Color::new(0.0, 1.0, 0.0, 1.0)); // Green terminal text
486+
487+
content.add_child(Box::new(prompt));
488+
489+
("Terminal".to_string(), Box::new(content))
490+
}
491+
492+
/// Create Code Editor window
493+
fn create_code_editor_window(&self) -> (String, Box<dyn UIElement>) {
494+
let mut content = Container::new("code_content".to_string())
495+
.with_direction(LayoutDirection::Vertical);
496+
497+
let code_text = Text::new("code_text".to_string(), "// Welcome to PrismaUI Code Editor\nfn main() {\n println!(\"Hello, World!\");\n}".to_string())
498+
.with_font_size(12.0)
499+
.with_color(Color::new(0.9, 0.9, 0.9, 1.0));
500+
501+
content.add_child(Box::new(code_text));
502+
503+
("Code Editor".to_string(), Box::new(content))
504+
}
505+
506+
/// Create Browser window
507+
fn create_browser_window(&self) -> (String, Box<dyn UIElement>) {
508+
let mut content = Container::new("browser_content".to_string())
509+
.with_direction(LayoutDirection::Vertical);
510+
511+
let address_bar = Text::new("address_bar".to_string(), "https://prismaui.dev".to_string())
512+
.with_font_size(14.0)
513+
.with_color(Color::new(0.1, 0.1, 0.1, 1.0));
514+
515+
let page_content = Text::new("page_content".to_string(), "Welcome to PrismaUI Browser\n\nA GPU-accelerated web browser built with Rust.".to_string())
516+
.with_font_size(14.0)
517+
.with_color(Color::new(0.2, 0.2, 0.2, 1.0));
518+
519+
content.add_child(Box::new(address_bar));
520+
content.add_child(Box::new(page_content));
521+
522+
("Safari".to_string(), Box::new(content))
523+
}
524+
525+
/// Create Calculator window
526+
fn create_calculator_window(&self) -> (String, Box<dyn UIElement>) {
527+
let mut content = Container::new("calculator_content".to_string())
528+
.with_direction(LayoutDirection::Vertical);
529+
530+
let display = Text::new("calc_display".to_string(), "0".to_string())
531+
.with_font_size(24.0)
532+
.with_color(Color::new(0.1, 0.1, 0.1, 1.0));
533+
534+
let buttons = Container::new("calc_buttons".to_string())
535+
.with_direction(LayoutDirection::Horizontal);
536+
537+
content.add_child(Box::new(display));
538+
content.add_child(Box::new(buttons));
539+
540+
("Calculator".to_string(), Box::new(content))
541+
}
542+
543+
/// Create Settings window
544+
fn create_settings_window(&self) -> (String, Box<dyn UIElement>) {
545+
let mut content = Container::new("settings_content".to_string())
546+
.with_direction(LayoutDirection::Vertical);
547+
548+
let title = Text::new("settings_title".to_string(), "System Preferences".to_string())
549+
.with_font_size(18.0)
550+
.with_color(Color::new(0.1, 0.1, 0.1, 1.0));
551+
552+
let options = Text::new("settings_options".to_string(), "• Display Settings\n• Audio Settings\n• Network Settings\n• Privacy Settings".to_string())
553+
.with_font_size(14.0)
554+
.with_color(Color::new(0.3, 0.3, 0.3, 1.0));
555+
556+
content.add_child(Box::new(title));
557+
content.add_child(Box::new(options));
558+
559+
("System Preferences".to_string(), Box::new(content))
560+
}
561+
562+
/// Create Music window
563+
fn create_music_window(&self) -> (String, Box<dyn UIElement>) {
564+
let mut content = Container::new("music_content".to_string())
565+
.with_direction(LayoutDirection::Vertical);
566+
567+
let title = Text::new("music_title".to_string(), "Music".to_string())
568+
.with_font_size(18.0)
569+
.with_color(Color::new(0.1, 0.1, 0.1, 1.0));
570+
571+
let playlist = Text::new("music_playlist".to_string(), "♪ Now Playing: Ambient Desktop Sounds\n♪ Next: Coding Beats\n♪ Queue: Lo-Fi Study Mix".to_string())
572+
.with_font_size(14.0)
573+
.with_color(Color::new(0.3, 0.3, 0.3, 1.0));
574+
575+
content.add_child(Box::new(title));
576+
content.add_child(Box::new(playlist));
577+
578+
("Music".to_string(), Box::new(content))
579+
}
580+
581+
/// Create Photos window
582+
fn create_photos_window(&self) -> (String, Box<dyn UIElement>) {
583+
let mut content = Container::new("photos_content".to_string())
584+
.with_direction(LayoutDirection::Vertical);
585+
586+
let title = Text::new("photos_title".to_string(), "Photos".to_string())
587+
.with_font_size(18.0)
588+
.with_color(Color::new(0.1, 0.1, 0.1, 1.0));
589+
590+
let gallery = Text::new("photos_gallery".to_string(), "📷 My Photos (42 items)\n📁 Screenshots\n📁 Desktop Wallpapers\n📁 GPU Renders".to_string())
591+
.with_font_size(14.0)
592+
.with_color(Color::new(0.3, 0.3, 0.3, 1.0));
593+
594+
content.add_child(Box::new(title));
595+
content.add_child(Box::new(gallery));
596+
597+
("Photos".to_string(), Box::new(content))
598+
}
599+
600+
/// Process queued app launches
601+
fn process_app_launches(&mut self) {
602+
if let Ok(mut queue) = APP_LAUNCH_QUEUE.lock() {
603+
while let Some(app_id) = queue.pop_front() {
604+
self.create_app_window(&app_id);
605+
}
606+
}
607+
}
608+
503609
/// Handle window resize
504610
fn resize(&mut self, new_size: PhysicalSize<u32>) {
505611
self.context.resize(new_size);
@@ -526,6 +632,9 @@ impl Compositor {
526632
// Update window manager
527633
self.window_manager.update();
528634

635+
// Process app launch queue
636+
self.process_app_launches();
637+
529638
// Update text layouts for UI elements that need it
530639
// TODO: This should be more efficient, only updating dirty text
531640
// self.update_text_layouts();

crates/prisma_compositor/src/ui.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ use crate::{
1010
// Beautiful UI components
1111
pub mod wallpaper;
1212
pub mod desktop_icon;
13+
pub mod dock;
1314

1415
pub use wallpaper::Wallpaper;
1516
pub use desktop_icon::{DesktopIcon, DesktopIconGrid, IconSize};
17+
pub use dock::{Dock, DockApp, DockEvent};
1618

1719
/// Layout constraints for flexible UI positioning
1820
#[derive(Debug, Clone)]

0 commit comments

Comments
 (0)