1use iced::widget::text::IntoFragment;
24use iced::{
25 Alignment::{self, Center},
26 Element, Length,
27 widget::{Column, center, column, container, row, rule, svg::Handle, table, text},
28};
29
30use crate::{Ferrix, Message, fl, icons::ERROR_ICON, widgets::link_button};
31
32mod battery;
33mod cpu;
34mod dashboard;
35mod distro;
36mod dmi;
37mod drm;
38mod env;
39mod export;
40mod groups;
41mod kernel;
42mod ram;
43mod settings;
44mod system;
45mod systemd;
46mod users;
47
48pub use kernel::KernelData;
49
50#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
51pub enum Page {
52 #[default]
56 Dashboard,
57 Processors,
58 Memory,
59 Storage,
60 DMI,
61 Battery,
62 Screen,
63
64 Distro,
68 SystemMisc,
69 Users,
70 Groups,
71 SystemManager,
72 Software,
73 Environment,
74 Sensors,
75
76 Kernel,
80 KModules,
81 Development,
82
83 Settings,
87 About,
88 Export,
89 Todo,
90}
91
92impl<'a> Page {
93 pub fn title(&'a self) -> iced::widget::Column<'a, Message> {
94 header_text(self.title_str())
95 }
96
97 pub fn title_str(&self) -> String {
98 match self {
99 Self::Dashboard => fl!("page-dashboard"),
100 Self::Processors => fl!("page-procs"),
101 Self::Memory => fl!("page-memory"),
102 Self::Storage => fl!("page-storage"),
103 Self::DMI => fl!("page-dmi"),
104 Self::Battery => fl!("page-battery"),
105 Self::Screen => fl!("page-screen"),
106 Self::Distro => fl!("page-distro"),
107 Self::Users => fl!("page-users"),
108 Self::Groups => fl!("page-groups"),
109 Self::SystemManager => fl!("page-sysmgr"),
110 Self::Software => fl!("page-software"),
111 Self::Environment => fl!("page-env"),
112 Self::Sensors => fl!("page-sensors"),
113 Self::Kernel => fl!("page-kernel"),
114 Self::KModules => fl!("page-kmods"),
115 Self::Development => fl!("page-dev"),
116 Self::SystemMisc => fl!("page-sysmisc"),
117 Self::Settings => fl!("page-settings"),
118 Self::About => fl!("page-about"),
119 Self::Export => fl!("page-export"),
120 Self::Todo => fl!("page-todo"),
121 }
122 }
123
124 pub fn page(&'a self, state: &'a Ferrix) -> Element<'a, Message> {
125 let page = match self {
126 Self::Dashboard => dashboard::dashboard(
127 state.proc_data.to_option(),
128 (
129 state.prev_proc_stat.to_option(),
130 state.curr_proc_stat.to_option(),
131 ),
132 state.ram_data.to_option(),
133 state.osrel_data.to_option(),
134 state.system.to_option(),
135 )
136 .into(),
137 Self::Processors => cpu::proc_page(
138 &state.proc_data,
139 &state.curr_proc_stat,
140 &state.prev_proc_stat,
141 )
142 .into(),
143 Self::Memory => ram::ram_page(&state.ram_data).into(),
144 Self::DMI => dmi::dmi_page(&state.dmi_data).into(),
145 Self::Battery => battery::bat_page(&state.bat_data).into(),
146 Self::Screen => drm::drm_page(&state.drm_data).into(),
147 Self::Distro => distro::distro_page(&state.osrel_data).into(),
148 Self::Kernel => kernel::kernel_page(&state.info_kernel).into(),
149 Self::SystemMisc => system::system_page(&state.system).into(),
150 Self::Users => users::users_page(&state.users_list).into(),
151 Self::Groups => groups::groups_page(&state.groups_list).into(),
152 Self::SystemManager => systemd::services_page(&state.sysd_services_list).into(),
153 Self::Environment => env::env_page(&state.system).into(),
154 Self::Settings => settings::settings_page(&state).into(),
155 Self::Export => export::export_page().into(),
156 Self::About => self.about_page().into(),
157 _ => self.todo_page(),
158 };
159
160 column![self.title(), page,].spacing(5).into()
161 }
162
163 fn todo_page(&self) -> Element<'a, Message> {
164 container(center(
165 text(fl!("page-todo-msg")).size(16).style(text::secondary),
166 ))
167 .into()
168 }
169
170 fn about_page(&'a self) -> container::Container<'a, Message> {
171 let img = iced::widget::svg("/usr/share/Ferrix/com.mskrasnov.Ferrix.svg")
172 .width(128)
173 .height(128);
174 let header = row![
175 img,
176 column![
177 text(fl!("about-hdr")).size(24),
178 text(format!(
179 "{}: {}, {}: {}",
180 fl!("about-ferrix"),
181 env!("CARGO_PKG_VERSION"),
182 fl!("about-flib"),
183 ferrix_lib::FX_LIB_VERSION,
184 ))
185 .size(14),
186 ]
187 .spacing(5),
188 ]
189 .align_y(Center)
190 .spacing(5);
191
192 let about_info = row![
193 column![
194 text(fl!("about-author-hdr")).style(text::secondary),
195 text(fl!("about-feedback-hdr")).style(text::secondary),
196 text(fl!("about-source-hdr")).style(text::secondary),
197 text("crates.io:").style(text::secondary),
198 text(fl!("about-blog")).style(text::secondary),
199 ]
200 .align_x(Alignment::End)
201 .spacing(3),
202 column![
203 row![
204 text(fl!("about-author")),
205 link_button("(GitHub)", "https://github.com/mskrasnov"),
206 ]
207 .spacing(5),
208 link_button("mskrasnov07 at ya dot ru", "mailto:mskrasnov07@ya.ru"),
209 link_button("GitHub", "https://github.com/mskrasnov/Ferrix"),
210 row![
211 link_button("ferrix-app", "https://crates.io/crates/ferrix-app"),
212 text(", "),
213 link_button("ferrix-lib", "https://crates.io/crates/ferrix-lib"),
214 ],
215 link_button("mskrasnov", "https://boosty.to/mskrasnov"),
216 ]
217 .spacing(3),
218 ]
219 .spacing(5);
220
221 let donate = column![
222 text(fl!("about-donate")),
223 link_button(fl!("about-donate-lbl"), "https://boosty.to/mskrasnov"),
224 ]
225 .spacing(5);
226
227 let contents = column![
228 column![header, rule::horizontal(1)].spacing(2),
229 about_info,
230 row![
231 text(fl!("about-support")).style(text::warning).size(16),
232 rule::horizontal(1)
233 ]
234 .align_y(Center)
235 .spacing(5),
236 donate,
237 ]
238 .spacing(5);
239
240 container(contents)
241 }
242}
243
244#[derive(Debug, Clone)]
245pub struct InfoRow<V> {
246 pub param_header: String,
247 pub value: Option<V>,
248}
249
250impl<V> InfoRow<V> {
251 pub fn new<P>(param: P, value: Option<V>) -> Self
252 where
253 P: Into<String>,
254 V: ToString,
255 {
256 Self {
257 param_header: param.into(),
258 value,
259 }
260 }
261}
262
263fn text_fmt_val<'a, V>(val: Option<V>) -> text::Text<'a>
264where
265 V: ToString + 'a,
266{
267 match val {
268 Some(val) if !val.to_string().is_empty() => text(val.to_string()),
269 Some(_) => text("N/A"),
270 None => text(""),
271 }
272}
273
274pub fn kv_info_table<'a, V>(rows: Vec<InfoRow<V>>) -> Element<'a, Message>
275where
276 V: ToString + Clone + 'a,
277{
278 let columns = [
279 table::column(hdr_name(fl!("hdr-param")), |row: InfoRow<V>| {
280 text(row.param_header)
281 }),
282 table::column(hdr_name(fl!("hdr-value")), |row: InfoRow<V>| {
283 text_fmt_val(row.value)
284 })
285 .width(Length::Fill),
286 ];
287
288 table(columns, rows).padding(2).width(Length::Fill).into()
289}
290
291fn hdr_name<'a, S: IntoFragment<'a>>(s: S) -> text::Text<'a> {
292 text(s).style(text::secondary)
293}
294
295fn header_text<'a>(txt: String) -> Column<'a, Message> {
296 column![text(txt).size(22), rule::horizontal(1)].spacing(2)
297}
298
299fn fmt_val<T>(val: Option<T>) -> Option<String>
300where
301 T: ToString + Copy,
302{
303 match val {
304 Some(val) => Some(val.to_string()),
305 None => None,
306 }
307}
308
309fn fmt_vec<T>(val: &Option<Vec<T>>) -> Option<String>
310where
311 T: ToString + Clone,
312{
313 match val {
314 Some(val) => {
315 let mut s = String::new();
316 for i in val {
317 s = format!("{s}{} ", i.to_string());
318 }
319 Some(s)
320 }
321 None => None,
322 }
323}
324
325fn fmt_bool(val: Option<bool>) -> Option<String> {
326 match val {
327 Some(val) => match val {
328 true => Some(fl!("bool-true")),
329 false => Some(fl!("bool-false")),
330 },
331 None => None,
332 }
333}
334
335fn loading_page<'a>() -> container::Container<'a, Message> {
336 container(center(
337 text(fl!("ldr-page-tooltip"))
338 .style(text::secondary)
339 .size(14),
340 ))
341}
342
343fn error_page<'a>(etext: &'a str) -> container::Container<'a, Message> {
344 container(center(
345 column![
346 row![
347 iced::widget::svg(Handle::from_memory(ERROR_ICON))
348 .width(20)
349 .height(20),
350 text(fl!("err-page-tooltip")).size(20),
351 ]
352 .align_y(Center)
353 .spacing(5),
354 text(etext).style(text::secondary).size(14),
355 ]
356 .align_x(Center)
357 .spacing(5),
358 ))
359}