spm_swift_package/ui/
spm_view.rs

1use crate::domain::builder::spm_builder::*;
2use crate::utils::theme_colors::*;
3use iced::widget::{center_x, checkbox, column, text};
4use iced::{Color, Element};
5
6pub type AppElement<'a> = Element<'a, Message>;
7
8#[derive(Debug, Clone)]
9pub enum Message {
10	ChangelogToggled(bool),
11	ReadmeToggled(bool),
12	SwiftPackageIndexToggled(bool),
13	SwiftLintToggled(bool),
14	IosToggled(bool),
15	MacOsToggled(bool),
16	TvOsToggled(bool),
17	WatchOsToggled(bool),
18	VisionOsToggled(bool),
19	InputChanged(String),
20	GenerateSPM,
21}
22
23#[derive(Default)]
24pub struct SpmView {
25	changelog: bool,
26	readme: bool,
27	swift_package_index: bool,
28	swiftlint: bool,
29	ios: bool,
30	macos: bool,
31	tvos: bool,
32	watchos: bool,
33	visionos: bool,
34	input_content: String,
35	platform_error: bool,
36}
37
38/// Entry point to launch the iced application for the SPM GUI
39pub fn run() -> iced::Result {
40	iced::application(SpmView::default, SpmView::update, SpmView::view).run()
41}
42
43impl SpmView {
44	/// Handles state updates for each message received from the UI
45	pub fn update(&mut self, message: Message) {
46		match message {
47			Message::ChangelogToggled(val) => self.changelog = val,
48			Message::ReadmeToggled(val) => self.readme = val,
49			Message::SwiftPackageIndexToggled(val) => self.swift_package_index = val,
50			Message::SwiftLintToggled(val) => self.swiftlint = val,
51
52			Message::IosToggled(val) => {
53				if val {
54					self.ios = true;
55					self.macos = false;
56					self.tvos = false;
57					self.watchos = false;
58					self.visionos = false;
59					self.platform_error = false;
60				} else {
61					self.ios = false;
62				}
63			}
64			Message::MacOsToggled(val) => {
65				if val {
66					self.ios = false;
67					self.macos = true;
68					self.tvos = false;
69					self.watchos = false;
70					self.visionos = false;
71					self.platform_error = false;
72				} else {
73					self.macos = false;
74				}
75			}
76			Message::TvOsToggled(val) => {
77				if val {
78					self.ios = false;
79					self.macos = false;
80					self.tvos = true;
81					self.watchos = false;
82					self.visionos = false;
83					self.platform_error = false;
84				} else {
85					self.tvos = false;
86				}
87			}
88			Message::WatchOsToggled(val) => {
89				if val {
90					self.ios = false;
91					self.macos = false;
92					self.tvos = false;
93					self.watchos = true;
94					self.visionos = false;
95					self.platform_error = false;
96				} else {
97					self.watchos = false;
98				}
99			}
100			Message::VisionOsToggled(val) => {
101				if val {
102					self.ios = false;
103					self.macos = false;
104					self.tvos = false;
105					self.watchos = false;
106					self.visionos = true;
107					self.platform_error = false;
108				} else {
109					self.visionos = false;
110				}
111			}
112			Message::InputChanged(s) => self.input_content = s,
113			Message::GenerateSPM => {
114				let mut platforms = Vec::new();
115				if self.ios {
116					platforms.push("iOS");
117				}
118				if self.macos {
119					platforms.push("macOS");
120				}
121				if self.tvos {
122					platforms.push("tvOS");
123				}
124				if self.watchos {
125					platforms.push("watchOS");
126				}
127				if self.visionos {
128					platforms.push("visionOS");
129				}
130
131				if platforms.is_empty() {
132					self.platform_error = true;
133					return;
134				} else {
135					self.platform_error = false;
136				}
137
138				let mut project_name = self.input_content.clone();
139				if project_name.trim().is_empty() {
140					project_name = "Library".to_string();
141				}
142
143				let mut files = Vec::new();
144				if self.changelog {
145					files.push("Changelog");
146				}
147				if self.readme {
148					files.push("Readme");
149				}
150				if self.swift_package_index {
151					files.push("Swift Package Index");
152				}
153				if self.swiftlint {
154					files.push("SwiftLint");
155				}
156
157				tokio::spawn(async move {
158					if (SpmBuilder::builder(&project_name, files, platforms).await).is_ok() {
159						let command = format!("cd {} && open Package.swift", project_name);
160						let _ = std::process::Command::new("sh")
161							.arg("-c")
162							.arg(&command)
163							.spawn();
164					}
165				});
166			}
167		}
168	}
169
170	/// Builds and returns the main application view layout
171	pub fn view(&self) -> AppElement<'_> {
172		column![
173			self.header_view(),
174			self.input_view(),
175			self.files_title_view(),
176			self.files_checkboxes_view(),
177			self.platforms_title_view(),
178			self.platforms_checkboxes_view(),
179			self.error_platform_view(),
180			self.generate_button_view(),
181		]
182		.spacing(16)
183		.into()
184	}
185
186	/// Returns the header/title section view
187	fn header_view(&self) -> AppElement<'_> {
188		column![
189			center_x(
190				text("SPM Swift Package")
191					.color(ThemeColors::ORANGE)
192					.size(32)
193			)
194			.height(20),
195			center_x(
196				text(
197					"Command Line Tools for macOS to create Swift Package Manager projects with desirable files."
198				)
199				.color(ThemeColors::GRAY)
200				.size(14)
201			)
202			.padding(16),
203		]
204		.spacing(16)
205		.into()
206	}
207
208	/// Returns the text input field view for the project name
209	fn input_view(&self) -> AppElement<'_> {
210		iced::widget::Container::new(
211			iced::widget::text_input("Insert the name of the project", &self.input_content)
212				.on_input(Message::InputChanged)
213				.padding(8)
214				.size(18),
215		)
216		.padding([0, 24])
217		.into()
218	}
219
220	/// Returns the view for the file selection section's title
221	fn files_title_view(&self) -> AppElement<'_> {
222		let title = text("Choose the files you want to include in your project:")
223			.color(Color::WHITE)
224			.size(18);
225
226		iced::widget::Container::new(title).padding([0, 24]).into()
227	}
228
229	/// Returns the view for the platform selection section's title
230	fn platforms_title_view(&self) -> AppElement<'_> {
231		let title = text("Choose the platforms you want to include in your project:")
232			.color(Color::WHITE)
233			.size(18);
234
235		iced::widget::Container::new(title).padding([0, 24]).into()
236	}
237
238	/// Returns the view containing all file checkboxes
239	fn files_checkboxes_view(&self) -> AppElement<'_> {
240		let mut col = column![];
241
242		for checkbox in self.view_files_checkboxes() {
243			col = col.push(checkbox);
244		}
245
246		col.spacing(8).into()
247	}
248
249	/// Returns the view containing all platform checkboxes
250	fn platforms_checkboxes_view(&self) -> AppElement<'_> {
251		let mut col = column![];
252
253		for checkbox in self.view_platforms_checkboxes() {
254			col = col.push(checkbox);
255		}
256
257		col.spacing(8).into()
258	}
259
260	/// Returns the view for the 'Generate SPM' button
261	fn generate_button_view(&self) -> AppElement<'_> {
262		iced::widget::Container::new(
263			iced::widget::button(text("Generate SPM").size(16).color(Color::WHITE))
264				.on_press(Message::GenerateSPM),
265		)
266		.padding([8, 24])
267		.into()
268	}
269
270	/// Returns the view for the error message when no platform is selected
271	fn error_platform_view(&self) -> AppElement<'_> {
272		if self.platform_error {
273			iced::widget::Container::new(
274				text("Please select at least one platform.")
275					.color([1.0, 0.2, 0.2])
276					.size(16),
277			)
278			.padding([0, 24])
279			.into()
280		} else {
281			iced::widget::Container::new("").into()
282		}
283	}
284
285	/// Builds a vector of checkbox rows for each selectable file
286	fn view_files_checkboxes(&self) -> Vec<AppElement<'_>> {
287		vec![
288			self.checkbox_row("Changelog", self.changelog, Message::ChangelogToggled),
289			self.checkbox_row("Readme", self.readme, Message::ReadmeToggled),
290			self.checkbox_row(
291				"Swift Package Index",
292				self.swift_package_index,
293				Message::SwiftPackageIndexToggled,
294			),
295			self.checkbox_row("SwiftLint", self.swiftlint, Message::SwiftLintToggled),
296		]
297	}
298
299	/// Builds a vector of checkbox rows for each selectable platform
300	fn view_platforms_checkboxes(&self) -> Vec<AppElement<'_>> {
301		vec![
302			self.checkbox_row("iOS", self.ios, Message::IosToggled),
303			self.checkbox_row("macOS", self.macos, Message::MacOsToggled),
304			self.checkbox_row("tvOS", self.tvos, Message::TvOsToggled),
305			self.checkbox_row("watchOS", self.watchos, Message::WatchOsToggled),
306			self.checkbox_row("visionOS", self.visionos, Message::VisionOsToggled),
307		]
308	}
309
310	/// Helper to create a row with a single checkbox and label
311	fn checkbox_row<'a>(
312		&self,
313		label: &'a str,
314		value: bool,
315		on_toggle: fn(bool) -> Message,
316	) -> AppElement<'a> {
317		iced::widget::Container::new(checkbox(value).label(label).on_toggle(on_toggle))
318			.padding([0, 24])
319			.into()
320	}
321}