1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.
use std::path::PathBuf;
use crate::{
action::Action,
components::{
discord_username::DiscordUsernameInputBox, footer::Footer, home::Home,
resource_allocation::ResourceAllocationInputBox, tab::Tab, Component,
},
config::{AppData, Config},
mode::{InputMode, Scene},
tui,
};
use color_eyre::eyre::Result;
use crossterm::event::KeyEvent;
use ratatui::prelude::Rect;
use sn_peers_acquisition::PeersArgs;
use tokio::sync::mpsc;
pub struct App {
pub config: Config,
pub app_data: AppData,
pub tick_rate: f64,
pub frame_rate: f64,
pub components: Vec<Box<dyn Component>>,
pub should_quit: bool,
pub should_suspend: bool,
pub input_mode: InputMode,
pub scene: Scene,
pub last_tick_key_events: Vec<KeyEvent>,
}
impl App {
pub fn new(
tick_rate: f64,
frame_rate: f64,
peers_args: PeersArgs,
safenode_path: Option<PathBuf>,
) -> Result<Self> {
let app_data = AppData::load()?;
let tab = Tab::default();
let home = Home::new(
app_data.allocated_disk_space,
&app_data.discord_username,
peers_args,
safenode_path,
)?;
let config = Config::new()?;
let discord_username_input =
DiscordUsernameInputBox::new(app_data.discord_username.clone());
let resource_allocation_input =
ResourceAllocationInputBox::new(app_data.allocated_disk_space)?;
let footer = Footer::default();
let scene = tab.get_current_scene();
Ok(Self {
config,
app_data,
tick_rate,
frame_rate,
components: vec![
Box::new(tab),
Box::new(footer),
Box::new(home),
Box::new(discord_username_input),
Box::new(resource_allocation_input),
],
should_quit: false,
should_suspend: false,
input_mode: InputMode::Navigation,
scene,
last_tick_key_events: Vec::new(),
})
}
pub async fn run(&mut self) -> Result<()> {
let (action_tx, mut action_rx) = mpsc::unbounded_channel();
let mut tui = tui::Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
// tui.mouse(true);
tui.enter()?;
for component in self.components.iter_mut() {
component.register_action_handler(action_tx.clone())?;
component.register_config_handler(self.config.clone())?;
component.init(tui.size()?)?;
}
loop {
if let Some(e) = tui.next().await {
match e {
tui::Event::Quit => action_tx.send(Action::Quit)?,
tui::Event::Tick => action_tx.send(Action::Tick)?,
tui::Event::Render => action_tx.send(Action::Render)?,
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
tui::Event::Key(key) => {
if self.input_mode == InputMode::Navigation {
if let Some(keymap) = self.config.keybindings.get(&self.scene) {
if let Some(action) = keymap.get(&vec![key]) {
info!("Got action: {action:?}");
action_tx.send(action.clone())?;
} else {
// If the key was not handled as a single key action,
// then consider it for multi-key combinations.
self.last_tick_key_events.push(key);
// Check for multi-key combinations
if let Some(action) = keymap.get(&self.last_tick_key_events) {
info!("Got action: {action:?}");
action_tx.send(action.clone())?;
}
}
};
} else if self.input_mode == InputMode::Entry {
for component in self.components.iter_mut() {
let send_back_actions = component.handle_events(Some(e.clone()))?;
for action in send_back_actions {
action_tx.send(action)?;
}
}
}
}
_ => {}
}
}
while let Ok(action) = action_rx.try_recv() {
if action != Action::Tick && action != Action::Render {
debug!("{action:?}");
}
match action {
Action::Tick => {
self.last_tick_key_events.drain(..);
}
Action::Quit => self.should_quit = true,
Action::Suspend => self.should_suspend = true,
Action::Resume => self.should_suspend = false,
Action::Resize(w, h) => {
tui.resize(Rect::new(0, 0, w, h))?;
tui.draw(|f| {
for component in self.components.iter_mut() {
let r = component.draw(f, f.size());
if let Err(e) = r {
action_tx
.send(Action::Error(format!("Failed to draw: {:?}", e)))
.unwrap();
}
}
})?;
}
Action::Render => {
tui.draw(|f| {
for component in self.components.iter_mut() {
let r = component.draw(f, f.size());
if let Err(e) = r {
action_tx
.send(Action::Error(format!("Failed to draw: {:?}", e)))
.unwrap();
}
}
})?;
}
Action::SwitchScene(scene) => {
info!("Scene swtiched to: {scene:?}");
self.scene = scene;
}
Action::SwitchInputMode(mode) => {
info!("Input mode switched to: {mode:?}");
self.input_mode = mode;
}
Action::StoreDiscordUserName(ref username) => {
debug!("Storing discord username: {username:?}");
self.app_data.discord_username.clone_from(username);
self.app_data.save()?;
}
Action::StoreAllocatedDiskSpace(space) => {
debug!("Storing allocated disk space: {space:?}");
self.app_data.allocated_disk_space = space;
self.app_data.save()?;
}
_ => {}
}
for component in self.components.iter_mut() {
if let Some(action) = component.update(action.clone())? {
action_tx.send(action)?
};
}
}
if self.should_suspend {
tui.suspend()?;
action_tx.send(Action::Resume)?;
tui = tui::Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
// tui.mouse(true);
tui.enter()?;
} else if self.should_quit {
tui.stop()?;
break;
}
}
tui.exit()?;
Ok(())
}
}