Skip to content

Commit b044150

Browse files
author
FreeSynergy
committed
fix: embed Settings inline, window frame for other apps, remove spawn_app
- SettingsApp lazily initialised and rendered inline in the content area - Other apps render an in-desktop window chrome via view_app_window_frame() - spawn_app() removed; apps connect via gRPC/IPC in future phases - DesktopMessage::SettingsMsg added for forwarding messages to embedded app - DesktopShell.settings_app field: Option<SettingsApp>
1 parent 618f58e commit b044150

1 file changed

Lines changed: 129 additions & 41 deletions

File tree

crates/fs-gui-workspace/src/shell.rs

Lines changed: 129 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ use crate::shell_layout::ShellLayout;
113113
use crate::taskbar::{default_apps, AppEntry};
114114
use crate::wallpaper::Wallpaper;
115115
use crate::window::{AppId, Window, WindowHost, WindowId, WindowManager};
116+
use fs_settings::SettingsApp;
116117

117118
#[cfg(feature = "iced")]
118119
use fs_gui_engine_iced::{
@@ -199,6 +200,10 @@ pub enum DesktopMessage {
199200
/// Fired by the `HotReloadWatcher` when `desktop-layout.toml` changes on disk.
200201
LayoutReloaded,
201202

203+
// ── Embedded app messages ─────────────────────────────────────────────────
204+
/// Forward a message to the embedded Settings app.
205+
SettingsMsg(fs_settings::app::Message),
206+
202207
// ── No-op / async completion ──────────────────────────────────────────────
203208
Noop,
204209
}
@@ -247,6 +252,11 @@ pub struct DesktopShell {
247252
// ── Wallpaper (G1.5) ──────────────────────────────────────────────────────
248253
pub wallpaper: Wallpaper,
249254

255+
// ── Embedded app states ───────────────────────────────────────────────────
256+
/// Lazily initialised when `AppId::Settings` is first opened.
257+
/// Rendered inline in the content area — no separate OS window.
258+
pub settings_app: Option<SettingsApp>,
259+
250260
// ── Layout mode (G1.9: replaces tiling_active bool) ──────────────────────
251261
/// Current window layout mode: Normal | Tiling | `FocusMode`.
252262
pub layout_mode: WindowLayoutMode,
@@ -313,6 +323,7 @@ impl Default for DesktopShell {
313323
notification_center_open: false,
314324
clock_time: Local::now().format("%H:%M").to_string(),
315325
clock_date: Local::now().format("%d.%m.%Y").to_string(),
326+
settings_app: None,
316327
}
317328
}
318329
}
@@ -326,7 +337,12 @@ impl DesktopShell {
326337
match msg {
327338
// ── Window management ─────────────────────────────────────────────
328339
DesktopMessage::OpenApp(app_id) => {
329-
Self::spawn_app(app_id);
340+
// Embedded apps render inline — no separate OS window.
341+
// External apps (Browser, Store, …) are future gRPC/IPC targets;
342+
// we show an in-desktop window frame for them.
343+
if app_id == AppId::Settings && self.settings_app.is_none() {
344+
self.settings_app = Some(SettingsApp::new());
345+
}
330346
self.lifecycle_bus
331347
.app_opened(app_id.name().to_lowercase().as_str());
332348
self.active_app = Some(app_id);
@@ -502,6 +518,13 @@ impl DesktopShell {
502518
}
503519
}
504520

521+
// ── Embedded app messages ─────────────────────────────────────────
522+
DesktopMessage::SettingsMsg(msg) => {
523+
if let Some(app) = &mut self.settings_app {
524+
return app.update(msg).map(DesktopMessage::SettingsMsg);
525+
}
526+
}
527+
505528
DesktopMessage::Noop => {}
506529
}
507530

@@ -552,33 +575,17 @@ impl DesktopShell {
552575
_ => None,
553576
};
554577
if let Some(app) = app_id {
555-
if app != AppId::Help {
556-
Self::spawn_app(app);
578+
if app == AppId::Settings && self.settings_app.is_none() {
579+
self.settings_app = Some(SettingsApp::new());
557580
}
558581
self.active_app = Some(app);
559582
self.lifecycle_bus
560583
.app_opened(app.name().to_lowercase().as_str());
561584
}
562585
}
563586

564-
/// Launch an external app binary as a detached child process.
565-
fn spawn_app(app_id: AppId) {
566-
let binary = match app_id {
567-
AppId::Browser => "fs-browser",
568-
AppId::Settings => "fs-settings",
569-
AppId::Profile => "fs-profile",
570-
AppId::Store => "fs-store",
571-
AppId::Lenses => "fs-lenses",
572-
AppId::Builder => "fs-builder",
573-
AppId::Tasks => "fs-tasks",
574-
AppId::Bots => "fs-bots",
575-
AppId::Ai => "fs-ai",
576-
AppId::Container => "fs-container",
577-
AppId::Managers => "fs-managers",
578-
AppId::Help => return,
579-
};
580-
let _ = std::process::Command::new(binary).spawn();
581-
}
587+
// spawn_app removed: all apps now render inline within the desktop.
588+
// External apps (Browser, Store, …) will connect via gRPC/IPC in future phases.
582589

583590
// ── Subscription ──────────────────────────────────────────────────────────
584591

@@ -1144,26 +1151,18 @@ impl DesktopShell {
11441151
let p = self.palette();
11451152

11461153
let content: Element<'_, DesktopMessage> = if let Some(app_id) = self.active_app {
1147-
let handle = svg_icon(app_id.icon(), 48.0, p.icon_color);
1148-
let icon_el: Element<'_, DesktopMessage> = svg(handle).width(48).height(48).into();
1149-
container(
1150-
column![
1151-
icon_el,
1152-
Space::new().height(16),
1153-
text(app_id.name()).size(20).color(p.cyan),
1154-
Space::new().height(8),
1155-
text(tr("shell-app-launched")).size(14).color(p.muted),
1156-
Space::new().height(16),
1157-
button(text(tr("shell-app-relaunch")).size(13))
1158-
.on_press(DesktopMessage::OpenApp(app_id))
1159-
.padding([8, 20]),
1160-
]
1161-
.align_x(Alignment::Center)
1162-
.spacing(4),
1163-
)
1164-
.center_x(Length::Fill)
1165-
.center_y(Length::Fill)
1166-
.into()
1154+
match app_id {
1155+
// ── Embedded: Settings renders its full UI inline ──────────────
1156+
AppId::Settings => {
1157+
if let Some(settings) = &self.settings_app {
1158+
settings.view().map(DesktopMessage::SettingsMsg)
1159+
} else {
1160+
self.view_app_window_frame(app_id)
1161+
}
1162+
}
1163+
// ── All other apps: in-desktop window frame ────────────────────
1164+
_ => self.view_app_window_frame(app_id),
1165+
}
11671166
} else {
11681167
// Render the shell layout via IcedLayoutInterpreter when available.
11691168
let descriptor = self.shell_layout.to_layout_descriptor();
@@ -1228,6 +1227,95 @@ impl DesktopShell {
12281227
.into()
12291228
}
12301229

1230+
// ── App window frame (for not-yet-embedded apps) ──────────────────────────
1231+
1232+
/// Render an in-desktop window chrome for apps that are not yet embedded.
1233+
///
1234+
/// Shows a title bar with the app icon + name and a close button, so the
1235+
/// desktop looks like a real windowing environment even before all apps are
1236+
/// fully embedded.
1237+
fn view_app_window_frame(&self, app_id: AppId) -> Element<'_, DesktopMessage> {
1238+
let p = self.palette();
1239+
let icon_handle = svg_icon(app_id.icon(), 24.0, p.icon_color);
1240+
let icon_el: Element<'_, DesktopMessage> = svg(icon_handle).width(24).height(24).into();
1241+
1242+
let title = text(app_id.name()).size(15).color(p.cyan);
1243+
1244+
// Close button: removes the active app view.
1245+
let close_btn = button(text("✕").size(14).color(p.muted))
1246+
.on_press(DesktopMessage::CloseWindow(
1247+
self.windows
1248+
.open_windows()
1249+
.iter()
1250+
.find(|w| w.app == app_id)
1251+
.map_or(crate::window::WindowId(0), |w| w.meta.id),
1252+
))
1253+
.padding([4, 8]);
1254+
1255+
let title_bar = container(
1256+
row![
1257+
icon_el,
1258+
Space::new().width(8),
1259+
title,
1260+
Space::new().width(Length::Fill),
1261+
close_btn
1262+
]
1263+
.align_y(Alignment::Center),
1264+
)
1265+
.width(Length::Fill)
1266+
.padding([8, 12])
1267+
.style(move |_| container::Style {
1268+
background: Some(iced::Background::Color(p.bg_chrome)),
1269+
border: Border {
1270+
color: p.border_accent,
1271+
width: 1.0,
1272+
radius: 0.0.into(),
1273+
},
1274+
..container::Style::default()
1275+
});
1276+
1277+
let body = container(column![
1278+
Space::new().height(Length::Fill),
1279+
row![
1280+
Space::new().width(Length::Fill),
1281+
column![
1282+
svg(svg_icon(app_id.icon(), 64.0, p.icon_color))
1283+
.width(64)
1284+
.height(64),
1285+
Space::new().height(16),
1286+
text(app_id.name()).size(22).color(p.cyan),
1287+
Space::new().height(8),
1288+
text(tr("shell-app-active")).size(13).color(p.muted),
1289+
]
1290+
.align_x(Alignment::Center)
1291+
.spacing(2),
1292+
Space::new().width(Length::Fill),
1293+
]
1294+
.align_y(Alignment::Center),
1295+
Space::new().height(Length::Fill),
1296+
])
1297+
.width(Length::Fill)
1298+
.height(Length::Fill)
1299+
.style(move |_| container::Style {
1300+
background: Some(iced::Background::Color(p.bg_content)),
1301+
..container::Style::default()
1302+
});
1303+
1304+
container(column![title_bar, body].spacing(0))
1305+
.width(Length::Fill)
1306+
.height(Length::Fill)
1307+
.style(move |_| container::Style {
1308+
border: Border {
1309+
color: p.border_accent,
1310+
width: 1.0,
1311+
radius: 4.0.into(),
1312+
},
1313+
..container::Style::default()
1314+
})
1315+
.padding(1)
1316+
.into()
1317+
}
1318+
12311319
// ── Taskbar ───────────────────────────────────────────────────────────────
12321320

12331321
fn view_taskbar(&self) -> Element<'_, DesktopMessage> {

0 commit comments

Comments
 (0)