Skip to main content

waterui_cli/winui/
backend.rs

1//! `WinUI` backend configuration and initialization.
2
3use std::path::{Path, PathBuf};
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    backend::Backend,
9    build::BuildOptions,
10    device::Artifact,
11    platform::{PackageOptions, TargetPlatform},
12    project::Project,
13    templates::{self, TemplateContext},
14    winui::platform::{build_winui, clean_winui, is_winui_platform, package_winui},
15};
16
17/// Configuration for the `WinUI` backend in a `WaterUI` project.
18///
19/// `[backend.winui]` in `Water.toml`
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub struct WinUiBackend {
22    #[serde(
23        default = "default_winui_project_path",
24        skip_serializing_if = "is_default_winui_project_path"
25    )]
26    project_path: PathBuf,
27}
28
29impl WinUiBackend {
30    /// Create a new `WinUI` backend configuration with default settings.
31    #[must_use]
32    pub fn new() -> Self {
33        Self {
34            project_path: default_winui_project_path(),
35        }
36    }
37
38    /// Set a custom project path (defaults to "winui").
39    #[must_use]
40    pub fn with_project_path(mut self, path: impl Into<PathBuf>) -> Self {
41        self.project_path = path.into();
42        self
43    }
44
45    /// Get the path to the `WinUI` project within the `WaterUI` project.
46    #[must_use]
47    pub const fn project_path(&self) -> &PathBuf {
48        &self.project_path
49    }
50
51    /// Check whether managed `WinUI` backend files differ from the current templates.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error when the application dependency graph or templates
56    /// cannot be resolved.
57    pub async fn requires_regeneration(project: &Project) -> eyre::Result<bool> {
58        let backend_dir = project.backend_path::<Self>();
59        let ctx = Self::template_context(project).await?;
60        for (relative, expected) in
61            templates::winui::rendered_outputs(&ctx, &project.winui_backend_crate_name())?
62        {
63            match std::fs::read(backend_dir.join(relative)) {
64                Ok(existing) if existing == expected => {}
65                Ok(_) | Err(_) => return Ok(true),
66            }
67        }
68        Ok(false)
69    }
70
71    async fn template_context(project: &Project) -> eyre::Result<TemplateContext> {
72        let manifest = project.manifest();
73        let app_name = manifest
74            .package
75            .name
76            .chars()
77            .filter(|c| c.is_alphanumeric())
78            .collect::<String>();
79        Ok(TemplateContext::for_project_manifest(
80            manifest,
81            project.crate_name().clone(),
82            app_name,
83            &project.resolved_framework().await?,
84        )
85        .with_backend_project_path(project.backend_path::<Self>())
86        .with_project_root_path(project.root().to_path_buf()))
87    }
88}
89
90impl Default for WinUiBackend {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl Backend for WinUiBackend {
97    const DEFAULT_PATH: &'static str = "winui";
98
99    // `WinUI` uses cargo's target directory for build caches.
100    // Since the `WinUI` project is a simple Rust binary crate, it uses the workspace target.
101    // No need to preserve local target - it's part of the workspace.
102    const CACHE_PATHS: &'static [&'static str] = &[];
103
104    fn path(&self) -> &Path {
105        &self.project_path
106    }
107
108    async fn init(project: &Project) -> Result<Self, crate::backend::FailToInitBackend> {
109        let project_path = default_winui_project_path();
110        let ctx = Self::template_context(project)
111            .await
112            .map_err(crate::backend::FailToInitBackend::Config)?;
113
114        templates::winui::scaffold(
115            &project.backend_path::<Self>(),
116            &ctx,
117            &project.winui_backend_crate_name(),
118        )
119        .await
120        .map_err(crate::backend::FailToInitBackend::Io)?;
121
122        Ok(Self { project_path })
123    }
124
125    fn supports(&self, platform: TargetPlatform) -> bool {
126        is_winui_platform(platform)
127    }
128
129    async fn build(
130        &self,
131        project: &Project,
132        _platform: TargetPlatform,
133        options: BuildOptions,
134    ) -> eyre::Result<PathBuf> {
135        build_winui(project, options).await
136    }
137
138    async fn package(
139        &self,
140        project: &Project,
141        _platform: TargetPlatform,
142        options: PackageOptions,
143    ) -> eyre::Result<Artifact> {
144        package_winui(project, options).await
145    }
146
147    async fn clean(&self, project: &Project, _platform: TargetPlatform) -> eyre::Result<()> {
148        clean_winui(project).await
149    }
150}
151
152fn default_winui_project_path() -> PathBuf {
153    PathBuf::from("winui")
154}
155
156fn is_default_winui_project_path(s: &Path) -> bool {
157    s == Path::new("winui")
158}