1use ferrix_lib::{
24 battery::BatInfo,
25 cpu::{Processors, Stat},
26 cpu_freq::CpuFreq,
27 drm::Video,
28 init::{BootTimestamps, Connection, SystemdServices},
29 net::Networks,
30 parts::Mounts,
31 ram::{RAM, Swaps},
32 soft::InstalledPackages,
33 sys::{Groups, KModules, Kernel, OsRelease, Users},
34 traits::ToJson,
35 vulnerabilities::Vulnerabilities,
36};
37use iced::{
38 Event, Task, color,
39 keyboard::{Event as Kevent, Key, Modifiers, key},
40 widget::{
41 Id,
42 operation::{self, AbsoluteOffset, RelativeOffset},
43 },
44};
45
46use crate::{
47 DataLoadingState, Page, SETTINGS_PATH, System,
48 dmi::DMIData,
49 export::{ExportData, ExportFormat, ExportMode},
50 ferrix::{Ferrix, FerrixData},
51 settings::{ChartLineThickness, FXSettings, Style},
52 styles::CPU_CHARTS_COLORS,
53 utils::{ToColor, get_home},
54 widgets::line_charts::LineSeries,
55};
56
57#[derive(Debug, Clone)]
58pub enum Message {
59 DataReceiver(DataReceiverMessage),
60 ExportManager(ExportManagerMessage),
61 Settings(SettingsMessage),
62 Buttons(ButtonsMessage),
63
64 SelectPage(Page),
65 Keyboard(KeyboardMessage),
66 Dummy,
67}
68
69impl Message {
70 pub fn update<'a>(self, state: &'a mut Ferrix) -> Task<Message> {
71 match self {
72 Self::DataReceiver(data) => {
73 data.update(&mut state.data, &mut state.settings, state.current_page)
74 }
75 Self::ExportManager(export) => export.update(state),
76 Self::Settings(settings) => settings.update(state),
77 Self::Buttons(buttons) => buttons.update(state),
78
79 Self::SelectPage(page) => state.select_page(page),
80 Self::Keyboard(keyboard) => keyboard.update(state),
81 Self::Dummy => Task::none(),
82 }
83 }
84}
85
86impl Ferrix {
87 fn select_page(&mut self, page: Page) -> Task<Message> {
88 self.current_page = page;
89 self.scrolled_area_id = page.scrolled_list_id();
90 Task::none()
91 }
92}
93
94#[derive(Debug, Clone)]
95pub enum DataReceiverMessage {
96 GetCPUData,
97 CPUDataReceived(DataLoadingState<Processors>),
98
99 AddCPUCoreLineSeries,
101 ChangeShowCPUChartElements(usize),
102
103 GetProcStat,
104 ProcStatReceived(DataLoadingState<Stat>),
105
106 GetCPUFrequency,
107 CPUFrequencyReceived(DataLoadingState<CpuFreq>),
108
109 GetCPUVulnerabilities,
110 CPUVulnerabilitiesReveived(DataLoadingState<Vulnerabilities>),
111
112 GetRAMData,
113 RAMDataReceived(DataLoadingState<RAM>),
114
115 GetSwapData,
116 SwapDataReceived(DataLoadingState<Swaps>),
117
118 AddTotalRAMUsage,
119
120 GetStorageData,
121 StorageDataReceived(DataLoadingState<Mounts>),
122
123 GetNetworksData,
124 NetworksDataReceived(DataLoadingState<Networks>),
125
126 GetDMIData,
127 DMIDataReceived(DataLoadingState<DMIData>),
128
129 GetBatInfo,
130 BatInfoReceived(DataLoadingState<BatInfo>),
131
132 GetDRMData,
133 DRMDataReceived(DataLoadingState<Video>),
134
135 GetOsReleaseData,
136 OsReleaseDataReceived(DataLoadingState<OsRelease>),
137
138 GetKernelData,
139 KernelDataReceived(DataLoadingState<Kernel>),
140
141 GetKModsData,
142 KModsDataReceived(DataLoadingState<KModules>),
143
144 GetUsersData,
145 UsersDataReceived(DataLoadingState<Users>),
146
147 GetGroupsData,
148 GroupsDataReceived(DataLoadingState<Groups>),
149
150 GetSystemdServices,
151 SystemdServicesReceived(
152 (
153 DataLoadingState<SystemdServices>,
154 DataLoadingState<BootTimestamps>,
155 ),
156 ),
157
158 GetPackagesList,
159 PackagesListReceived(DataLoadingState<InstalledPackages>),
160
161 GetSystemData,
162 SystemDataReceived(DataLoadingState<System>),
163}
164
165impl DataReceiverMessage {
166 pub fn update<'a>(
167 self,
168 fx: &'a mut FerrixData,
169 settings: &'a mut FXSettings,
170 cur_page: Page,
171 ) -> Task<Message> {
172 match self {
173 Self::CPUDataReceived(state) => {
174 fx.proc_data = state;
175 Task::none()
176 }
177 Self::GetCPUData => Task::perform(
178 async move {
179 let proc = Processors::new();
180 match proc {
181 Ok(proc) => DataLoadingState::Loaded(proc),
182 Err(why) => DataLoadingState::Error(why.to_string()),
183 }
184 },
185 |val| Message::DataReceiver(Self::CPUDataReceived(val)),
186 ),
187 Self::ProcStatReceived(state) => {
188 if fx.curr_proc_stat.is_some() {
189 fx.prev_proc_stat = fx.curr_proc_stat.clone();
190 } else if fx.curr_proc_stat.is_none() && fx.prev_proc_stat.is_none() {
191 fx.prev_proc_stat = state.clone();
192 }
193 fx.curr_proc_stat = state;
194 Task::none()
195 }
196 Self::GetProcStat => Task::perform(
197 async move {
198 let stat = Stat::new();
199 match stat {
200 Ok(stat) => DataLoadingState::Loaded(stat),
201 Err(why) => DataLoadingState::Error(why.to_string()),
202 }
203 },
204 |val| Message::DataReceiver(Self::ProcStatReceived(val)),
205 ),
206 Self::AddCPUCoreLineSeries => {
217 let curr_proc = &fx.curr_proc_stat;
218 let prev_proc = &fx.prev_proc_stat;
219
220 if curr_proc.is_none() || prev_proc.is_none() {
221 return Task::none();
222 }
223 let curr_proc = curr_proc.to_option().unwrap();
224 let prev_proc = prev_proc.to_option().unwrap();
225
226 if curr_proc.cpus.len() != prev_proc.cpus.len() {
227 return Task::none();
228 }
229 let len = curr_proc.cpus.len();
230
231 let colors_set = &settings.chart_colors.colors;
232 let def_colors = CPU_CHARTS_COLORS;
233
234 for id in 0..len {
235 let percent =
236 curr_proc.cpus[id].usage_percentage(Some(prev_proc.cpus[id])) as f64;
237
238 if fx.show_cpus_chart.get(&id).is_none() {
239 let name = format!("CPU #{id}");
240 let color = {
241 let hm_color = colors_set.get(&name);
242 match hm_color {
243 Some(col) => col.to_color(),
244 None => {
245 if def_colors.len() - 1 < id {
246 color!(255, 255, 255)
247 } else {
248 def_colors[id]
249 }
250 }
251 }
252 };
253 let mut line = LineSeries::new(name, color, fx.show_chart_elements);
254 line.push(percent);
255
256 fx.cpu_usage_chart.push_series(line);
257 fx.show_cpus_chart.insert(id);
258 } else {
259 fx.cpu_usage_chart.push_to(id, percent);
260 }
261 }
262
263 Task::none()
264 }
265 Self::ChangeShowCPUChartElements(elems) => {
266 fx.show_chart_elements = elems;
267
268 fx.cpu_usage_chart.set_max_values(elems);
269 fx.ram_usage_chart.set_max_values(elems);
270
271 Task::none()
272 }
273 Self::CPUFrequencyReceived(state) => {
274 fx.cpu_freq = state;
275 Task::none()
276 }
277 Self::GetCPUFrequency => Task::perform(
278 async move {
279 let cpu_freq = CpuFreq::new();
280 match cpu_freq {
281 Ok(cpu_freq) => DataLoadingState::Loaded(cpu_freq),
282 Err(why) => DataLoadingState::Error(why.to_string()),
283 }
284 },
285 |val| Message::DataReceiver(DataReceiverMessage::CPUFrequencyReceived(val)),
286 ),
287 Self::CPUVulnerabilitiesReveived(state) => {
288 fx.cpu_vulnerabilities = state;
289 Task::none()
290 }
291 Self::GetCPUVulnerabilities => Task::perform(
292 async move {
293 let vuln = Vulnerabilities::new();
294 match vuln {
295 Ok(vuln) => DataLoadingState::Loaded(vuln),
296 Err(why) => DataLoadingState::Error(why.to_string()),
297 }
298 },
299 |val| Message::DataReceiver(Self::CPUVulnerabilitiesReveived(val)),
300 ),
301 Self::StorageDataReceived(state) => {
302 fx.storages = state;
303 Task::none()
304 }
305 Self::GetStorageData => Task::perform(
306 async move {
307 let storage = Mounts::new();
308 match storage {
309 Ok(storage) => DataLoadingState::Loaded(storage),
310 Err(why) => DataLoadingState::Error(why.to_string()),
311 }
312 },
313 |val| Message::DataReceiver(DataReceiverMessage::StorageDataReceived(val)),
314 ),
315 Self::NetworksDataReceived(state) => {
316 fx.networks = state;
317 Task::none()
318 }
319 Self::GetNetworksData => Task::perform(
320 async move {
321 let net = Networks::new();
322 match net {
323 Ok(net) => DataLoadingState::Loaded(net),
324 Err(why) => DataLoadingState::Error(why.to_string()),
325 }
326 },
327 |val| Message::DataReceiver(DataReceiverMessage::NetworksDataReceived(val)),
328 ),
329 Self::DMIDataReceived(state) => {
330 if state.some_value() && fx.is_polkit {
331 fx.dmi_data = state;
332 } else if !fx.is_polkit {
333 fx.dmi_data = state;
334 }
335 Task::none()
336 }
337 Self::GetDMIData => {
338 if !fx.is_polkit && fx.dmi_data.is_none() && cur_page == Page::DMI {
339 fx.is_polkit = true;
340 Task::perform(async move { crate::dmi::get_dmi_data().await }, |val| {
341 Message::DataReceiver(Self::DMIDataReceived(val))
342 })
343 } else {
344 Task::none()
345 }
346 }
347 Self::BatInfoReceived(state) => {
348 fx.bat_data = state;
349 Task::none()
350 }
351 Self::GetBatInfo => Task::perform(
352 async move {
353 let bat = BatInfo::new();
354 match bat {
355 Ok(bat) => DataLoadingState::Loaded(bat),
356 Err(why) => DataLoadingState::Error(why.to_string()),
357 }
358 },
359 |val| Message::DataReceiver(Self::BatInfoReceived(val)),
360 ),
361 Self::DRMDataReceived(state) => {
362 fx.drm_data = state;
363 Task::none()
364 }
365 Self::GetDRMData => Task::perform(
366 async move {
367 let drm = Video::new();
368 match drm {
369 Ok(drm) => DataLoadingState::Loaded(drm),
370 Err(why) => DataLoadingState::Error(why.to_string()),
371 }
372 },
373 |val| Message::DataReceiver(Self::DRMDataReceived(val)),
374 ),
375 Self::RAMDataReceived(state) => {
376 fx.ram_data = state;
377 Task::none()
378 }
379 Self::GetRAMData => Task::perform(
380 async move {
381 let ram = RAM::new();
382 match ram {
383 Ok(ram) => DataLoadingState::Loaded(ram),
384 Err(why) => DataLoadingState::Error(why.to_string()),
385 }
386 },
387 |val| Message::DataReceiver(Self::RAMDataReceived(val)),
388 ),
389 Self::SwapDataReceived(state) => {
390 fx.swap_data = state;
391 Task::none()
392 }
393 Self::GetSwapData => Task::perform(
394 async move {
395 let swap = Swaps::new();
396 match swap {
397 Ok(swap) => DataLoadingState::Loaded(swap),
398 Err(why) => DataLoadingState::Error(why.to_string()),
399 }
400 },
401 |val| Message::DataReceiver(Self::SwapDataReceived(val)),
402 ),
403 Self::AddTotalRAMUsage => {
404 let ram = &fx.ram_data;
405 let swap = &fx.swap_data;
406
407 if ram.is_none() {
408 return Task::none();
409 }
410 let ram = ram.to_option().unwrap();
411 let ram_usage = ram.usage_percentage().unwrap_or(0.);
412
413 let colors_set = &settings.chart_colors.colors;
414 let def_colors = CPU_CHARTS_COLORS;
415 let ram_color = match colors_set.get("RAM") {
416 Some(col) => col.to_color(),
417 None => color!(128, 64, 255),
418 };
419
420 if fx.ram_usage_chart.series_count() == 0 {
421 let mut ram_line = LineSeries::new(
422 format!("RAM"),
423 ram_color,
424 fx.show_chart_elements,
425 );
426 ram_line.push(ram_usage);
427 fx.ram_usage_chart.push_series(ram_line);
428 } else {
429 fx.ram_usage_chart.push_to(0, ram_usage);
430 }
431
432 if let Some(swap) = swap.to_option() {
433 let len = swap.swaps.len();
434 let current_series_cnt = fx.ram_usage_chart.series_count();
435
436 for id in 0..len {
437 let series_idx = id + 1;
438 let swap_usage = swap.swaps[id].usage_percentage().unwrap_or(0.);
439 let swap_name = swap.swaps[id].filename.clone();
440
441 if series_idx >= current_series_cnt {
442 let color = match colors_set.get(&swap_name) {
443 Some(col) => col.to_color(),
444 None => {
445 if def_colors.len() - 1 < id {
446 color!(255, 255, 128)
447 } else {
448 def_colors[id]
449 }
450 }
451 };
452 let mut line = LineSeries::new(
453 swap.swaps[id].filename.clone(),
454 color,
455 fx.show_chart_elements,
456 );
457 line.push(swap_usage);
458
459 fx.ram_usage_chart.push_series(line);
460 } else {
461 fx.ram_usage_chart.push_to(series_idx, swap_usage);
462 }
463 }
464 }
465 Task::none()
466 }
467 Self::OsReleaseDataReceived(state) => {
468 fx.osrel_data = state;
469 Task::none()
470 }
471 Self::GetOsReleaseData => Task::perform(
472 async move {
473 let osrel = OsRelease::new();
474 match osrel {
475 Ok(osrel) => DataLoadingState::Loaded(osrel),
476 Err(why) => DataLoadingState::Error(why.to_string()),
477 }
478 },
479 |val| Message::DataReceiver(Self::OsReleaseDataReceived(val)),
480 ),
481 Self::KernelDataReceived(state) => {
482 fx.kernel_data = state;
483 Task::none()
484 }
485 Self::GetKernelData => Task::perform(
486 async move {
487 let kern = Kernel::new();
488 match kern {
489 Ok(kern) => {
490 DataLoadingState::Loaded(kern)
492 }
493 Err(why) => DataLoadingState::Error(why.to_string()),
494 }
495 },
496 |val| Message::DataReceiver(Self::KernelDataReceived(val)),
497 ),
498 Self::KModsDataReceived(state) => {
499 fx.kmods_data = state;
500 Task::none()
501 }
502 Self::GetKModsData => Task::perform(
503 async move {
504 let kern = KModules::new();
505 match kern {
506 Ok(mut kern) => {
507 kern.modules.sort_by_key(|module| module.name.clone());
508 DataLoadingState::Loaded(kern)
509 }
510 Err(why) => DataLoadingState::Error(why.to_string()),
511 }
512 },
513 |val| Message::DataReceiver(Self::KModsDataReceived(val)),
514 ),
515 Self::UsersDataReceived(state) => {
516 fx.users_list = state;
517 Task::none()
518 }
519 Self::GetUsersData => Task::perform(
520 async move {
521 let users = Users::new();
522 match users {
523 Ok(mut users) => {
524 users.users.sort_by_key(|usr| usr.uid);
525 DataLoadingState::Loaded(users)
526 }
527 Err(why) => DataLoadingState::Error(why.to_string()),
528 }
529 },
530 |val| Message::DataReceiver(Self::UsersDataReceived(val)),
531 ),
532 Self::GroupsDataReceived(state) => {
533 fx.groups_list = state;
534 Task::none()
535 }
536 Self::GetGroupsData => Task::perform(
537 async move {
538 let groups = Groups::new();
539 match groups {
540 Ok(mut groups) => {
541 groups.groups.sort_by_key(|grp| grp.gid);
542 DataLoadingState::Loaded(groups)
543 }
544 Err(why) => DataLoadingState::Error(why.to_string()),
545 }
546 },
547 |val| Message::DataReceiver(Self::GroupsDataReceived(val)),
548 ),
549 Self::SystemdServicesReceived((sysd_services, boot_time)) => {
550 fx.sysd_services_list = sysd_services;
551 fx.boot_time = boot_time;
552 Task::none()
553 }
554 Self::GetSystemdServices => Task::perform(
555 async move {
556 let conn = Connection::session().await;
557 if let Err(why) = conn {
558 return (
559 DataLoadingState::Error(why.to_string()),
560 DataLoadingState::Loading,
561 );
562 }
563 let conn = conn.unwrap();
564
565 let srv_list = SystemdServices::new_from_connection(&conn).await;
566 match srv_list {
567 Ok(mut srv) => {
568 if srv.timestamps.total == 0 {
569 let a = srv.timestamps.calc_boot_time();
570 if let Err(why) = a {
571 return (
572 DataLoadingState::Loaded(srv),
573 DataLoadingState::Error(why.to_string()),
574 );
575 }
576 }
577
578 let boot_time = srv.timestamps;
579 (
580 DataLoadingState::Loaded(srv),
581 DataLoadingState::Loaded(boot_time),
582 )
583 }
584 Err(why) => (
585 DataLoadingState::Error(why.to_string()),
586 DataLoadingState::Error("".to_string()),
587 ),
588 }
589 },
590 |val| Message::DataReceiver(Self::SystemdServicesReceived(val)),
591 ),
592 Self::SystemDataReceived(state) => {
593 fx.system = state;
594 Task::none()
595 }
596 Self::GetPackagesList => Task::perform(
597 async move {
598 let pkglist = InstalledPackages::get();
599 match pkglist {
600 Ok(pkglist) => DataLoadingState::Loaded(pkglist),
601 Err(why) => DataLoadingState::Error(why.to_string()),
602 }
603 },
604 |val| Message::DataReceiver(Self::PackagesListReceived(val)),
605 ),
606 Self::PackagesListReceived(state) => {
607 fx.installed_pkgs_list = state;
608 Task::none()
609 }
610 Self::GetSystemData => Task::perform(
611 async move {
612 let sys = System::new();
613 match sys {
614 Ok(sys) => DataLoadingState::Loaded(sys),
615 Err(why) => DataLoadingState::Error(why.to_string()),
616 }
617 },
618 |val| Message::DataReceiver(Self::SystemDataReceived(val)),
619 ),
620 }
621 }
622}
623
624pub type ExportToFilePath = String;
625
626#[derive(Debug, Clone)]
627pub enum ExportManagerMessage {
628 ExportData(ExportToFilePath),
629 ExportFormatSelected(ExportFormat),
630 ExportModeSelected(ExportMode),
631}
632
633impl ExportManagerMessage {
634 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
635 match self {
636 Self::ExportData(path) => fx.export_data(&path),
637 _ => Task::none(),
638 }
639 }
640}
641
642impl Ferrix {
643 fn export_data(&mut self, path: &str) -> Task<Message> {
644 let json = ExportData::from(self)
645 .to_json()
646 .unwrap_or("{error}".to_string());
647 let _ = std::fs::write(path, json);
648 Task::none()
649 }
650}
651
652#[derive(Debug, Clone)]
653pub enum SettingsMessage {
654 ChangeStyle(Style),
655 ChangeUpdatePeriod(u8),
656 ChangeChartsUpdatePeriod(u8),
657 ChangeChartLineThickness(ChartLineThickness),
658 SetChartItemColor(String, (u8, u8, u8)),
659}
660
661impl SettingsMessage {
662 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
663 match self {
664 Self::ChangeStyle(style) => fx.change_style(style),
665 Self::ChangeUpdatePeriod(secs) => fx.change_update_period(secs),
666 Self::ChangeChartsUpdatePeriod(secs) => fx.change_charts_update_period(secs),
667 Self::ChangeChartLineThickness(thick) => fx.change_line_thickness(thick),
668 Self::SetChartItemColor(item, color) => fx.set_chart_item_color(item, color),
669 }
670 }
671}
672
673impl Ferrix {
674 fn change_style(&mut self, style: Style) -> Task<Message> {
675 self.settings.style = style;
676 self.data.cpu_usage_chart.set_style(&style.to_theme());
677 self.data.ram_usage_chart.set_style(&style.to_theme());
678 Task::none()
679 }
680
681 fn change_update_period(&mut self, per: u8) -> Task<Message> {
682 self.settings.update_period = per;
683 Task::none()
684 }
685
686 fn change_charts_update_period(&mut self, per: u8) -> Task<Message> {
687 self.settings.charts_update_period_nsecs = per;
688 Task::none()
689 }
690
691 fn change_line_thickness(&mut self, thick: ChartLineThickness) -> Task<Message> {
692 self.settings.chart_line_thickness = thick;
693 self.data.cpu_usage_chart.set_line_thickness(thick);
694 self.data.ram_usage_chart.set_line_thickness(thick);
695
696 Task::none()
697 }
698
699 fn set_chart_item_color(&mut self, item: String, color: (u8, u8, u8)) -> Task<Message> {
700 self.settings.chart_colors.colors.insert(item, color);
701 self.save_settings()
702 }
703}
704
705#[derive(Debug, Clone)]
706pub enum ButtonsMessage {
707 LinkButtonPressed(String),
708 SaveSettingsButtonPressed,
709 CopyButtonPressed(String),
710
711 ChangeLegendShow(bool),
712 ProcessorSelected(usize),
713}
714
715impl ButtonsMessage {
716 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
717 match self {
718 Self::LinkButtonPressed(url) => fx.go_to_url(&url),
719 Self::SaveSettingsButtonPressed => fx.save_settings(),
720 Self::CopyButtonPressed(s) => iced::clipboard::write(s),
721 Self::ChangeLegendShow(show) => fx.set_show_charts_legend(show),
722 Self::ProcessorSelected(id) => fx.proc_selected(id),
723 }
724 }
725}
726
727impl Ferrix {
728 fn go_to_url(&self, url: &str) -> Task<Message> {
729 let _ = crate::utils::xdg_open(url);
731 Task::none()
732 }
733
734 fn save_settings(&mut self) -> Task<Message> {
735 let _ = self
737 .settings
738 .write(get_home().join(".config").join(SETTINGS_PATH));
739 Task::none()
740 }
741
742 fn set_show_charts_legend(&mut self, show: bool) -> Task<Message> {
743 self.data.cpu_usage_chart.set_show_legend(show);
744 self.data.ram_usage_chart.set_show_legend(show);
745 self.data.show_charts_legend = show;
746 Task::none()
747 }
748
749 fn proc_selected(&mut self, id: usize) -> Task<Message> {
750 self.data.selected_proc = id;
751 Task::none()
752 }
753}
754
755#[derive(Debug, Clone)]
756pub enum KeyboardMessage {
757 Event(Event),
758}
759
760fn get_id(page: Page, m: Modifiers) -> Id {
761 if m.shift() {
762 Id::new(page.scrolled_list_id().unwrap_or(""))
763 } else {
764 Id::new(page.page_id())
765 }
766}
767
768const SCROLL_UP: f32 = -20.;
769const SCROLL_DOWN: f32 = 20.;
770
771fn scroll_up(page: Page, m: Modifiers) -> Task<Message> {
772 let id = get_id(page, m);
773 operation::scroll_by(
774 id,
775 AbsoluteOffset {
776 x: 0.,
777 y: SCROLL_UP,
778 },
779 )
780}
781
782fn scroll_down(page: Page, m: Modifiers) -> Task<Message> {
783 let id = get_id(page, m);
784 operation::scroll_by(
785 id,
786 AbsoluteOffset {
787 x: 0.,
788 y: SCROLL_DOWN,
789 },
790 )
791}
792
793fn scroll_sidebar_up() -> Task<Message> {
794 operation::scroll_by(
795 Id::new("sidebar"),
796 AbsoluteOffset {
797 x: 0.,
798 y: SCROLL_UP,
799 },
800 )
801}
802
803fn scroll_sidebar_down() -> Task<Message> {
804 operation::scroll_by(
805 Id::new("sidebar"),
806 AbsoluteOffset {
807 x: 0.,
808 y: SCROLL_DOWN,
809 },
810 )
811}
812
813fn snap_up(page: Page) -> Task<Message> {
814 let id = Id::new(page.page_id());
815 operation::snap_to(id, RelativeOffset::START)
816}
817
818fn snap_down(page: Page) -> Task<Message> {
819 let id = Id::new(page.page_id());
820 operation::snap_to(id, RelativeOffset::END)
821}
822
823impl KeyboardMessage {
824 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
825 match self {
826 Self::Event(event) => match event {
827 Event::Keyboard(Kevent::KeyPressed {
828 key: Key::Named(key::Named::ArrowDown),
829 modifiers,
830 ..
831 }) if !modifiers.control() => scroll_down(fx.current_page, modifiers),
832 Event::Keyboard(Kevent::KeyPressed {
833 key: Key::Named(key::Named::ArrowUp),
834 modifiers,
835 ..
836 }) if !modifiers.control() => scroll_up(fx.current_page, modifiers),
837 Event::Keyboard(Kevent::KeyPressed {
838 key: Key::Named(key::Named::ArrowDown),
839 modifiers,
840 ..
841 }) if modifiers.control() => scroll_sidebar_down(),
842 Event::Keyboard(Kevent::KeyPressed {
843 key: Key::Named(key::Named::ArrowUp),
844 modifiers,
845 ..
846 }) if modifiers.control() => scroll_sidebar_up(),
847 Event::Keyboard(Kevent::KeyPressed {
848 key: Key::Named(key::Named::PageDown),
849 ..
850 }) => snap_down(fx.current_page),
851 Event::Keyboard(Kevent::KeyPressed {
852 key: Key::Named(key::Named::PageUp),
853 ..
854 }) => snap_up(fx.current_page),
855 Event::Keyboard(Kevent::KeyPressed {
856 key: Key::Named(key::Named::F1),
857 ..
858 }) => fx.select_page(Page::About),
859 Event::Keyboard(Kevent::KeyPressed {
860 key: Key::Named(key::Named::F2),
861 ..
862 }) => fx.select_page(Page::Export),
863 Event::Keyboard(Kevent::KeyPressed {
864 key: Key::Named(key::Named::F9),
865 ..
866 }) => fx.select_page(Page::Settings),
867 Event::Keyboard(Kevent::KeyPressed {
868 key: Key::Named(key::Named::Tab),
869 modifiers,
870 ..
871 }) if modifiers.control() => fx.select_page(if modifiers.shift() {
872 fx.current_page.prev_page()
873 } else {
874 fx.current_page.next_page()
875 }),
876 _ => Task::none(),
877 },
878 }
879 }
880}