spm_swift_package/ui/
spm_view.rs

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