network + ui: stratum settings setup and list ui, pass platform callbacks to network tabs, add horizontal line view

This commit is contained in:
ardocrat 2023-06-21 02:13:47 +03:00
parent 7dba27e6d1
commit 4ad134c5e3
10 changed files with 249 additions and 140 deletions

View file

@ -12,10 +12,10 @@ network:
autorun: Autorun autorun: Autorun
disabled_server: 'Enable integrated node or choose another connection method by pressing %{dots} in the top-left corner of the screen.' disabled_server: 'Enable integrated node or choose another connection method by pressing %{dots} in the top-left corner of the screen.'
sync_status: sync_status:
server_restarting: Server is restarting node_restarting: Node is restarting
server_down: Server is down node_down: Node is down
initial: Server is starting initial: Node is starting
no_sync: Server is running no_sync: Node is running
awaiting_peers: Waiting for peers awaiting_peers: Waiting for peers
header_sync: Downloading headers header_sync: Downloading headers
header_sync_percent: 'Downloading headers: %{percent}%' header_sync_percent: 'Downloading headers: %{percent}%'
@ -31,7 +31,7 @@ sync_status:
tx_hashset_save: Finalizing chain state tx_hashset_save: Finalizing chain state
body_sync: Downloading blocks body_sync: Downloading blocks
body_sync_percent: 'Downloading blocks: %{percent}%' body_sync_percent: 'Downloading blocks: %{percent}%'
shutdown: Server is shutting down shutdown: Node is shutting down
network_node: network_node:
header: Header header: Header
block: Block block: Block
@ -55,11 +55,15 @@ network_metrics:
difficulty_window: 'Difficulty window %{size}' difficulty_window: 'Difficulty window %{size}'
network_mining: network_mining:
loading: Mining will be available after the synchronization loading: Mining will be available after the synchronization
server_setup: Stratum server setup
enable_server: Enable server enable_server: Enable server
disabled_server: 'Enable stratum server at %{address} or change settings by selecting %{settings} at the bottom of the screen.' server_setting: 'Enable stratum server or change more settings by selecting %{settings} at the bottom of the screen.'
info: 'Mining server is enabled, you can change settings by selecting %{settings} at the bottom of the screen. Data is updating when devices are connected.' info: 'Mining server is enabled, you can change settings by selecting %{settings} at the bottom of the screen. Data is updating when devices are connected.'
address: IP Address no_ip_addresses: There are no available IP addresses on your system, the server cannot be started, check your network connectivity.
wallet: Wallet Address choose_ip_address: 'Choose IP Address:'
change_port: 'Change port:'
ip_address: IP Address
rewards_wallet: Wallet for rewards
server: Stratum server server: Stratum server
miners: Miners miners: Miners
devices: Devices devices: Devices

View file

@ -12,10 +12,10 @@ network:
autorun: Автозапуск autorun: Автозапуск
disabled_server: 'Включите встроенный узел или выберите другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.' disabled_server: 'Включите встроенный узел или выберите другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.'
sync_status: sync_status:
server_restarting: Сервер перезапускается node_restarting: Узел перезапускается
server_down: Сервер выключен node_down: Узел выключен
initial: Запуск сервера initial: Запуск узла
no_sync: Сервер запущен no_sync: Узел запущен
awaiting_peers: Ожидание пиров awaiting_peers: Ожидание пиров
header_sync: Загрузка заголовков header_sync: Загрузка заголовков
header_sync_percent: 'Загрузка заголовков: %{percent}%' header_sync_percent: 'Загрузка заголовков: %{percent}%'
@ -31,7 +31,7 @@ sync_status:
tx_hashset_save: Сохранение состояния цепи tx_hashset_save: Сохранение состояния цепи
body_sync: Загрузка блоков body_sync: Загрузка блоков
body_sync_percent: 'Загрузка блоков: %{percent}%' body_sync_percent: 'Загрузка блоков: %{percent}%'
shutdown: Выключение сервера shutdown: Выключение узла
network_node: network_node:
header: Заголовок header: Заголовок
block: Блок block: Блок
@ -55,11 +55,15 @@ network_metrics:
difficulty_window: 'Окно сложности %{size}' difficulty_window: 'Окно сложности %{size}'
network_mining: network_mining:
loading: Майнинг будет доступен после синхронизации loading: Майнинг будет доступен после синхронизации
server_setup: Настройка stratum-сервера
enable_server: Включить сервер enable_server: Включить сервер
disabled_server: 'Включите stratum-сервер по адресу %{address} или измените настройки, выбрав %{settings} внизу экрана.' server_setting: 'Включите stratum-сервер или измените больше настроек, выбрав %{settings} внизу экрана.'
info: 'Сервер майнинга запущен, вы можете изменить настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.' info: 'Сервер майнинга запущен, вы можете изменить настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.'
address: IP Адрес no_ip_addresses: В вашей системе отсутствуют доступные IP адреса, запуск сервера невозможен, проверьте ваше подключение к сети.
wallet: Адрес кошелька choose_ip_address: 'Выберите IP адрес:'
change_port: 'Измените порт:'
ip_address: IP Адрес
rewards_wallet: Кошелёк для наград
server: Stratum-сервер server: Stratum-сервер
miners: Майнеры miners: Майнеры
devices: Устройства devices: Устройства

View file

@ -64,7 +64,7 @@ impl Modal {
/// Default width of the content. /// Default width of the content.
const DEFAULT_WIDTH: i64 = 380; const DEFAULT_WIDTH: i64 = 380;
/// Create open and closeable [`Modal`] with center position. /// Create open and closeable Modal with center position.
pub fn new(id: ModalId, location: ModalLocation) -> Self { pub fn new(id: ModalId, location: ModalLocation) -> Self {
Self { Self {
id, id,
@ -76,45 +76,45 @@ impl Modal {
} }
} }
/// Setup position of [`Modal`] on the screen. /// Setup position of Modal on the screen.
pub fn position(mut self, position: ModalPosition) -> Self { pub fn position(mut self, position: ModalPosition) -> Self {
self.position = position; self.position = position;
self self
} }
/// Check if [`Modal`] is open. /// Check if Modal is open.
pub fn is_open(&self) -> bool { pub fn is_open(&self) -> bool {
self.open.load(Ordering::Relaxed) self.open.load(Ordering::Relaxed)
} }
/// Mark [`Modal`] closed. /// Mark Modal closed.
pub fn close(&self) { pub fn close(&self) {
self.open.store(false, Ordering::Relaxed); self.open.store(false, Ordering::Relaxed);
} }
/// Setup possibility to close [`Modal`]. /// Setup possibility to close Modal.
pub fn closeable(self, closeable: bool) -> Self { pub fn closeable(self, closeable: bool) -> Self {
self.closeable.store(closeable, Ordering::Relaxed); self.closeable.store(closeable, Ordering::Relaxed);
self self
} }
/// Disable possibility to close [`Modal`]. /// Disable possibility to close Modal.
pub fn disable_closing(&self) { pub fn disable_closing(&self) {
self.closeable.store(false, Ordering::Relaxed); self.closeable.store(false, Ordering::Relaxed);
} }
/// Check if [`Modal`] is closeable. /// Check if Modal is closeable.
pub fn is_closeable(&self) -> bool { pub fn is_closeable(&self) -> bool {
self.closeable.load(Ordering::Relaxed) self.closeable.load(Ordering::Relaxed)
} }
/// Set title text. /// Set title text on Modal creation.
pub fn title(mut self, title: String) -> Self { pub fn title(mut self, title: String) -> Self {
self.title = Some(title.to_uppercase()); self.title = Some(title.to_uppercase());
self self
} }
/// Show [`Modal`] with provided content. /// Show Modal with provided content.
pub fn ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) { pub fn ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
let width = min(ui.available_width() as i64 - 20, Self::DEFAULT_WIDTH) as f32; let width = min(ui.available_width() as i64 - 20, Self::DEFAULT_WIDTH) as f32;
@ -252,11 +252,6 @@ impl Modal {
ui.painter().set(bg_idx, bg_shape); ui.painter().set(bg_idx, bg_shape);
// Draw line below title. // Draw line below title.
let line_size = Vec2::new(ui.available_width(), 1.0); View::horizontal_line(ui, Colors::STROKE);
let (line_rect, _) = ui.allocate_exact_size(line_size, Sense::hover());
let painter = ui.painter();
painter.hline(line_rect.x_range(),
painter.round_to_pixel(line_rect.center().y),
View::DEFAULT_STROKE);
} }
} }

View file

@ -32,7 +32,7 @@ use crate::Settings;
pub trait NetworkTab { pub trait NetworkTab {
fn get_type(&self) -> NetworkTabType; fn get_type(&self) -> NetworkTabType;
fn ui(&mut self, ui: &mut egui::Ui); fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks);
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -67,7 +67,7 @@ impl Default for Network {
} }
impl Network { impl Network {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, _: &dyn PlatformCallbacks) { pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
egui::TopBottomPanel::top("network_title") egui::TopBottomPanel::top("network_title")
.resizable(false) .resizable(false)
.frame(egui::Frame { .frame(egui::Frame {
@ -98,7 +98,7 @@ impl Network {
.. Default::default() .. Default::default()
}) })
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
self.current_tab.ui(ui); self.current_tab.ui(ui, cb);
}); });
} }

View file

@ -18,6 +18,7 @@ use grin_servers::DiffBlock;
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER}; use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; use crate::gui::views::{Network, NetworkTab, NetworkTabType, View};
use crate::node::Node; use crate::node::Node;
@ -33,7 +34,7 @@ impl NetworkTab for NetworkMetrics {
NetworkTabType::Metrics NetworkTabType::Metrics
} }
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let server_stats = Node::get_stats(); let server_stats = Node::get_stats();
// Show message when node is not running or loading spinner when metrics are not available. // Show message when node is not running or loading spinner when metrics are not available.
if server_stats.is_none() || server_stats.as_ref().unwrap().diff_stats.height == 0 { if server_stats.is_none() || server_stats.as_ref().unwrap().diff_stats.height == 0 {

View file

@ -12,13 +12,18 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::fmt::format;
use std::net::IpAddr;
use std::str::FromStr;
use chrono::{DateTime, NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
use egui::{RichText, Rounding, ScrollArea, Stroke}; use egui::{RichText, Rounding, ScrollArea, Stroke};
use grin_chain::SyncStatus; use grin_chain::SyncStatus;
use grin_servers::WorkerStats; use grin_servers::WorkerStats;
use pnet::ipnetwork::IpNetwork;
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, COMPUTER_TOWER, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, PLUGS, PLUGS_CONNECTED, POLYGON}; use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, COMPUTER_TOWER, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, PLUGS, PLUGS_CONNECTED, POLYGON, WRENCH};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; use crate::gui::views::{Network, NetworkTab, NetworkTabType, View};
use crate::node::Node; use crate::node::Node;
use crate::Settings; use crate::Settings;
@ -31,8 +36,9 @@ impl NetworkTab for NetworkMining {
NetworkTabType::Mining NetworkTabType::Mining
} }
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let server_stats = Node::get_stats(); let server_stats = Node::get_stats();
// Show message when node is not running or loading spinner when mining are not available. // Show message when node is not running or loading spinner when mining are not available.
if !server_stats.is_some() || Node::get_sync_status().unwrap() != SyncStatus::NoSync { if !server_stats.is_some() || Node::get_sync_status().unwrap() != SyncStatus::NoSync {
if !Node::is_running() { if !Node::is_running() {
@ -50,63 +56,158 @@ impl NetworkTab for NetworkMining {
return; return;
} }
// Stratum mining server address. let stratum_stats = &server_stats.as_ref().unwrap().stratum_stats;
let stratum_address = Settings::node_config_to_read()
// Stratum server address + port from config.
let saved_stratum_addr = Settings::node_config_to_read()
.members.clone() .members.clone()
.server.stratum_mining_config.unwrap() .server.stratum_mining_config.unwrap()
.stratum_server_addr.unwrap(); .stratum_server_addr.unwrap();
let (stratum_addr, stratum_port) = saved_stratum_addr.split_once(":").unwrap();
let stratum_stats = &server_stats.as_ref().unwrap().stratum_stats; // List of available ip addresses.
if !stratum_stats.is_running && !Node::is_stratum_server_starting() { let mut addresses = Vec::new();
// Show Stratum setup when mining server is not enabled. for net_if in pnet::datalink::interfaces() {
View::center_content(ui, 162.0, |ui| { for ip in net_if.ips {
let text = t!( if ip.is_ipv4() {
"network_mining.disabled_server", addresses.push(ip.ip());
"address" => stratum_address, }
"settings" => FADERS }
); }
ui.label(RichText::new(text)
// Show error message when IP addresses are not available on the system.
if addresses.is_empty() {
View::center_content(ui, 52.0, |ui| {
ui.label(RichText::new(t!("network_mining.no_ip_addresses"))
.size(16.0) .size(16.0)
.color(Colors::INACTIVE_TEXT) .color(Colors::INACTIVE_TEXT)
); );
let mut addresses = Vec::new();
for net_if in pnet::datalink::interfaces() {
for ip in net_if.ips {
if ip.is_ipv4() {
addresses.push(ip);
}
}
}
if addresses.len() != 0 {
}
ui.add_space(10.0);
View::button(ui, t!("network_mining.enable_server"), Colors::GOLD, || {
Node::start_stratum_server();
});
ui.add_space(2.0);
// Check if stratum server is enabled at config.
let stratum_enabled = Settings::node_config_to_read()
.members.clone()
.server.stratum_mining_config.unwrap()
.enable_stratum_server.unwrap();
View::checkbox(ui, stratum_enabled, t!("network.autorun"), || {
let mut w_node_config = Settings::node_config_to_update();
w_node_config.members
.server.stratum_mining_config.as_mut().unwrap()
.enable_stratum_server = Some(!stratum_enabled);
w_node_config.save();
});
}); });
return; return;
}
// Show stratum server setup when mining server is not enabled.
if !stratum_stats.is_running && !Node::is_stratum_server_starting() {
ScrollArea::vertical()
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.add_space(6.0);
View::sub_title(ui,
format!("{} {}", WRENCH, t!("network_mining.server_setup")));
ui.add_space(4.0);
View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(6.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("network_mining.choose_ip_address"))
.size(16.0)
.color(Colors::GRAY)
);
ui.add_space(10.0);
if addresses.len() != 0 {
let saved_ip_addr = &IpAddr::from_str(stratum_addr).unwrap();
let mut selected_addr = saved_ip_addr;
// Set first IP address as current if saved is not present at system.
if !addresses.contains(selected_addr) {
selected_addr = addresses.get(0).unwrap();
}
// Show available IP addresses on the system.
let _ = addresses.chunks(2).map(|x| {
if x.len() == 2 {
ui.columns(2, |columns| {
let addr0 = x.get(0).unwrap();
columns[0].vertical_centered(|ui| {
View::radio_value(ui,
&mut selected_addr,
addr0,
addr0.to_string());
});
let addr1 = x.get(1).unwrap();
columns[1].vertical_centered(|ui| {
View::radio_value(ui,
&mut selected_addr,
addr1,
addr1.to_string());
})
});
ui.add_space(12.0);
} else {
let addr = x.get(0).unwrap();
View::radio_value(ui,
&mut selected_addr,
addr,
addr.to_string());
ui.add_space(4.0);
}
}).collect::<Vec<_>>();
// Save stratum server address at config if it was changed.
if saved_ip_addr != selected_addr {
let addr_to_save = format!("{}:{}", selected_addr, stratum_port);
let mut w_node_config = Settings::node_config_to_update();
w_node_config.members
.server.stratum_mining_config.as_mut().unwrap()
.stratum_server_addr = Some(addr_to_save);
w_node_config.save();
}
}
ui.label(RichText::new(t!("network_mining.change_port"))
.size(16.0)
.color(Colors::GRAY)
);
// Show button to choose server port.
ui.add_space(6.0);
View::button(ui, stratum_port.to_string(), Colors::WHITE, || {
//TODO: Open modal to change value
cb.show_keyboard();
});
ui.add_space(14.0);
View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(6.0);
// Show message about stratum server config.
let text = t!(
"network_mining.server_setting",
"address" => saved_stratum_addr,
"settings" => FADERS
);
ui.label(RichText::new(text)
.size(16.0)
.color(Colors::INACTIVE_TEXT)
);
ui.add_space(8.0);
// Show button to enable server.
View::button(ui, t!("network_mining.enable_server"), Colors::GOLD, || {
Node::start_stratum_server();
});
ui.add_space(2.0);
let stratum_enabled = Settings::node_config_to_read()
.members.clone()
.server.stratum_mining_config.unwrap()
.enable_stratum_server.unwrap();
// Show stratum server autorun checkbox.
View::checkbox(ui, stratum_enabled, t!("network.autorun"), || {
let mut w_node_config = Settings::node_config_to_update();
w_node_config.members
.server.stratum_mining_config.as_mut().unwrap()
.enable_stratum_server = Some(!stratum_enabled);
w_node_config.save();
});
});
ui.add_space(6.0);
});
return;
} else if Node::is_stratum_server_starting() { } else if Node::is_stratum_server_starting() {
// Show loading spinner when mining server is starting.
ui.centered_and_justified(|ui| { ui.centered_and_justified(|ui| {
View::big_loading_spinner(ui); View::big_loading_spinner(ui);
}); });
@ -118,8 +219,8 @@ impl NetworkTab for NetworkMining {
ui.columns(2, |columns| { ui.columns(2, |columns| {
columns[0].vertical_centered(|ui| { columns[0].vertical_centered(|ui| {
View::rounded_box(ui, View::rounded_box(ui,
stratum_address, saved_stratum_addr,
t!("network_mining.address"), t!("network_mining.ip_address"),
[true, false, true, false]); [true, false, true, false]);
}); });
columns[1].vertical_centered(|ui| { columns[1].vertical_centered(|ui| {
@ -131,7 +232,7 @@ impl NetworkTab for NetworkMining {
.replace("http://", ""); .replace("http://", "");
View::rounded_box(ui, View::rounded_box(ui,
wallet_address, wallet_address,
t!("network_mining.wallet"), t!("network_mining.rewards_wallet"),
[false, true, false, true]); [false, true, false, true]);
}); });
}); });
@ -198,6 +299,10 @@ impl NetworkTab for NetworkMining {
// Show workers stats or info text when possible. // Show workers stats or info text when possible.
let workers_size = stratum_stats.worker_stats.len(); let workers_size = stratum_stats.worker_stats.len();
if workers_size != 0 && stratum_stats.num_workers > 0 { if workers_size != 0 && stratum_stats.num_workers > 0 {
ui.add_space(4.0);
View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(4.0);
ScrollArea::vertical() ScrollArea::vertical()
.auto_shrink([false; 2]) .auto_shrink([false; 2])
.id_source("stratum_workers_scroll") .id_source("stratum_workers_scroll")
@ -240,9 +345,8 @@ fn draw_worker_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
ui.add_space(4.0); ui.add_space(4.0);
} }
ui.horizontal(|ui| { ui.horizontal_wrapped(|ui| {
ui.add_space(6.0); ui.vertical_centered_justified(|ui| {
ui.vertical(|ui| {
let mut rect = ui.available_rect_before_wrap(); let mut rect = ui.available_rect_before_wrap();
rect.set_height(WORKER_UI_HEIGHT); rect.set_height(WORKER_UI_HEIGHT);
ui.painter().rect( ui.painter().rect(
@ -259,25 +363,25 @@ fn draw_worker_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
ui.add_space(2.0); ui.add_space(2.0);
ui.horizontal_top(|ui| { ui.horizontal_top(|ui| {
let (status_text, status_icon) = match ws.is_connected { let (status_text, status_icon, status_color) = match ws.is_connected {
true => { (t!("network_mining.connected"), PLUGS_CONNECTED) } true => { (t!("network_mining.connected"), PLUGS_CONNECTED, Colors::BLACK) }
false => { (t!("network_mining.disconnected"), PLUGS) } false => { (t!("network_mining.disconnected"), PLUGS, Colors::INACTIVE_TEXT) }
}; };
ui.add_space(5.0); ui.add_space(5.0);
ui.heading(RichText::new(status_icon) ui.heading(RichText::new(status_icon)
.color(Colors::BLACK) .color(status_color)
.size(18.0)); .size(18.0));
ui.add_space(2.0); ui.add_space(2.0);
// Draw worker ID. // Draw worker ID.
ui.heading(RichText::new(&ws.id) ui.heading(RichText::new(&ws.id)
.color(Colors::BLACK) .color(status_color)
.size(18.0)); .size(18.0));
ui.add_space(3.0); ui.add_space(3.0);
// Draw worker status. // Draw worker status.
ui.heading(RichText::new(status_text) ui.heading(RichText::new(status_text)
.color(Colors::BLACK) .color(status_color)
.size(18.0)); .size(18.0));
}); });
ui.horizontal_top(|ui| { ui.horizontal_top(|ui| {
@ -347,7 +451,6 @@ fn draw_worker_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
.size(16.0)); .size(16.0));
}); });
ui.add_space(2.0);
}); });
}); });
} }

View file

@ -18,6 +18,7 @@ use grin_servers::PeerStats;
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK}; use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; use crate::gui::views::{Network, NetworkTab, NetworkTabType, View};
use crate::node::Node; use crate::node::Node;
@ -29,7 +30,7 @@ impl NetworkTab for NetworkNode {
NetworkTabType::Node NetworkTabType::Node
} }
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let server_stats = Node::get_stats(); let server_stats = Node::get_stats();
// Show message when node is not running or loading spinner when stats are not available. // Show message when node is not running or loading spinner when stats are not available.
if !server_stats.is_some() { if !server_stats.is_some() {

View file

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
use grin_core::global::ChainTypes; use grin_core::global::ChainTypes;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{NetworkTab, NetworkTabType}; use crate::gui::views::{NetworkTab, NetworkTabType};
use crate::Settings; use crate::Settings;
@ -24,6 +25,6 @@ impl NetworkTab for NetworkSettings {
NetworkTabType::Settings NetworkTabType::Settings
} }
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
} }
} }

View file

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use egui::{Button, PointerState, Response, RichText, Sense, Spinner, Widget}; use egui::{AboveOrBelow, Button, PointerState, Response, RichText, ScrollArea, Sense, Spinner, Widget, WidgetText};
use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke}; use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke};
use egui::epaint::text::TextWrapping; use egui::epaint::text::TextWrapping;
use egui::text::{LayoutJob, TextFormat}; use egui::text::{LayoutJob, TextFormat};
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{CHECK_SQUARE, CIRCLE, RADIO_BUTTON, SQUARE}; use crate::gui::icons::{CARET_DOWN, CHECK_SQUARE, CIRCLE, RADIO_BUTTON, SQUARE};
pub struct View; pub struct View;
@ -62,17 +62,17 @@ impl View {
ui.add_space(4.0); ui.add_space(4.0);
} }
/// Temporary button click optimization for touch screens. /// Temporary click optimization for touch screens, return `true` if it was clicked.
fn on_button_click(ui: &mut egui::Ui, resp: Response, action: impl FnOnce()) { fn touched(ui: &mut egui::Ui, resp: Response) -> bool {
let drag_resp = resp.interact(Sense::click_and_drag()); let drag_resp = resp.interact(Sense::click_and_drag());
// Clear pointer event if dragging is out of button area // Clear pointer event if dragging is out of button area
if drag_resp.dragged() && !ui.rect_contains_pointer(drag_resp.rect) { if drag_resp.dragged() && !ui.rect_contains_pointer(drag_resp.rect) {
ui.input_mut().pointer = PointerState::default(); ui.input_mut().pointer = PointerState::default();
} }
// Call click action if button is clicked or drag released
if drag_resp.drag_released() || drag_resp.clicked() { if drag_resp.drag_released() || drag_resp.clicked() {
(action)(); return true
}; }
false
} }
/// Title button with transparent background fill color, contains only icon. /// Title button with transparent background fill color, contains only icon.
@ -80,13 +80,13 @@ impl View {
ui.scope(|ui| { ui.scope(|ui| {
// Disable stroke around title buttons on hover // Disable stroke around title buttons on hover
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE; ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
let wt = RichText::new(icon.to_string()).size(24.0).color(Colors::TITLE); let wt = RichText::new(icon.to_string()).size(24.0).color(Colors::TITLE);
let br = Button::new(wt) let br = Button::new(wt)
.fill(Colors::TRANSPARENT) .fill(Colors::TRANSPARENT)
.ui(ui); .ui(ui);
if Self::touched(ui, br) {
Self::on_button_click(ui, br, action); (action)();
}
}); });
} }
@ -96,34 +96,32 @@ impl View {
true => { Colors::TITLE } true => { Colors::TITLE }
false => { Colors::TEXT } false => { Colors::TEXT }
}; };
let wt = RichText::new(icon.to_string()).size(24.0).color(text_color);
let stroke = match active { let stroke = match active {
true => { Stroke::NONE } true => { Stroke::NONE }
false => { Self::DEFAULT_STROKE } false => { Self::DEFAULT_STROKE }
}; };
let color = match active { let color = match active {
true => { Colors::FILL } true => { Colors::FILL }
false => { Colors::WHITE } false => { Colors::WHITE }
}; };
let br = Button::new(wt) let br = Button::new(RichText::new(icon.to_string()).size(24.0).color(text_color))
.stroke(stroke) .stroke(stroke)
.fill(color) .fill(color)
.ui(ui); .ui(ui);
if Self::touched(ui, br) {
Self::on_button_click(ui, br, action); (action)();
}
} }
/// Draw [`Button`] with specified background fill color. /// Draw [`Button`] with specified background fill color.
pub fn button(ui: &mut egui::Ui, text: String, fill_color: Color32, action: impl FnOnce()) { pub fn button(ui: &mut egui::Ui, text: String, fill_color: Color32, action: impl FnOnce()) {
let wt = RichText::new(text.to_uppercase()).size(18.0).color(Colors::BUTTON); let br = Button::new(RichText::new(text.to_uppercase()).size(18.0).color(Colors::BUTTON))
let br = Button::new(wt)
.stroke(Self::DEFAULT_STROKE) .stroke(Self::DEFAULT_STROKE)
.fill(fill_color) .fill(fill_color)
.ui(ui); .ui(ui);
if Self::touched(ui, br) {
Self::on_button_click(ui, br, action); (action)();
}
} }
/// Draw rounded box with some value and label in the middle, /// Draw rounded box with some value and label in the middle,
@ -212,31 +210,33 @@ impl View {
true => { (format!("{} {}", CHECK_SQUARE, text), Colors::BUTTON) } true => { (format!("{} {}", CHECK_SQUARE, text), Colors::BUTTON) }
false => { (format!("{} {}", SQUARE, text), Colors::TEXT) } false => { (format!("{} {}", SQUARE, text), Colors::TEXT) }
}; };
let br = Button::new(RichText::new(text_value).size(18.0).color(color))
let wt = RichText::new(text_value).size(18.0).color(color);
let br = Button::new(wt)
.frame(false) .frame(false)
.stroke(Stroke::NONE) .stroke(Stroke::NONE)
.fill(Colors::TRANSPARENT) .fill(Colors::TRANSPARENT)
.ui(ui); .ui(ui);
if Self::touched(ui, br) {
Self::on_button_click(ui, br, callback); (callback)();
}
} }
/// Draw the radio button with callback on select. /// Show a [`RadioButton`]. It is selected if `*current_value == selected_value`.
pub fn radio_button(ui: &mut egui::Ui, selected: bool, text: String, callback: impl FnOnce()) { /// If clicked, `selected_value` is assigned to `*current_value`.
let (text_value, color) = match selected { pub fn radio_value<T: PartialEq>(ui: &mut egui::Ui, current: &mut T, value: T, text: String) {
true => { (format!("{} {}", RADIO_BUTTON, text), Colors::BUTTON) } let mut response = ui.radio(*current == value, text);
false => { (format!("{} {}", CIRCLE, text), Colors::TEXT) } if Self::touched(ui, response.clone()) && *current != value {
}; *current = value;
response.mark_changed();
}
}
let wt = RichText::new(text_value).size(18.0).color(color); /// Draw horizontal line
let br = Button::new(wt) pub fn horizontal_line(ui: &mut egui::Ui, color: Color32) {
.frame(false) let line_size = egui::Vec2::new(ui.available_width(), 1.0);
.stroke(Stroke::NONE) let (line_rect, _) = ui.allocate_exact_size(line_size, Sense::hover());
.fill(Colors::TRANSPARENT) let painter = ui.painter();
.ui(ui); painter.hline(line_rect.x_range(),
painter.round_to_pixel(line_rect.center().y),
Self::on_button_click(ui, br, callback); Stroke { width: 1.0, color });
} }
} }

View file

@ -221,13 +221,13 @@ impl Node {
}; };
if Node::is_restarting() { if Node::is_restarting() {
return t!("sync_status.server_restarting") return t!("sync_status.node_restarting")
} }
let sync_status = Self::get_sync_status(); let sync_status = Self::get_sync_status();
if sync_status.is_none() { if sync_status.is_none() {
return t!("sync_status.server_down") return t!("sync_status.node_down")
} }
match sync_status.unwrap() { match sync_status.unwrap() {