diff --git a/locales/en.yml b/locales/en.yml index 38f54cc..34e7892 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -12,10 +12,10 @@ network: autorun: Autorun disabled_server: 'Enable integrated node or choose another connection method by pressing %{dots} in the top-left corner of the screen.' sync_status: - server_restarting: Server is restarting - server_down: Server is down - initial: Server is starting - no_sync: Server is running + node_restarting: Node is restarting + node_down: Node is down + initial: Node is starting + no_sync: Node is running awaiting_peers: Waiting for peers header_sync: Downloading headers header_sync_percent: 'Downloading headers: %{percent}%' @@ -31,7 +31,7 @@ sync_status: tx_hashset_save: Finalizing chain state body_sync: Downloading blocks body_sync_percent: 'Downloading blocks: %{percent}%' - shutdown: Server is shutting down + shutdown: Node is shutting down network_node: header: Header block: Block @@ -55,11 +55,15 @@ network_metrics: difficulty_window: 'Difficulty window %{size}' network_mining: loading: Mining will be available after the synchronization + server_setup: Stratum server setup 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.' - address: IP Address - wallet: Wallet Address + no_ip_addresses: There are no available IP addresses on your system, the server cannot be started, check your network connectivity. + choose_ip_address: 'Choose IP Address:' + change_port: 'Change port:' + ip_address: IP Address + rewards_wallet: Wallet for rewards server: Stratum server miners: Miners devices: Devices diff --git a/locales/ru.yml b/locales/ru.yml index 9b3dc0f..1fa4921 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -12,10 +12,10 @@ network: autorun: Автозапуск disabled_server: 'Включите встроенный узел или выберите другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.' sync_status: - server_restarting: Сервер перезапускается - server_down: Сервер выключен - initial: Запуск сервера - no_sync: Сервер запущен + node_restarting: Узел перезапускается + node_down: Узел выключен + initial: Запуск узла + no_sync: Узел запущен awaiting_peers: Ожидание пиров header_sync: Загрузка заголовков header_sync_percent: 'Загрузка заголовков: %{percent}%' @@ -31,7 +31,7 @@ sync_status: tx_hashset_save: Сохранение состояния цепи body_sync: Загрузка блоков body_sync_percent: 'Загрузка блоков: %{percent}%' - shutdown: Выключение сервера + shutdown: Выключение узла network_node: header: Заголовок block: Блок @@ -55,11 +55,15 @@ network_metrics: difficulty_window: 'Окно сложности %{size}' network_mining: loading: Майнинг будет доступен после синхронизации + server_setup: Настройка stratum-сервера enable_server: Включить сервер - disabled_server: 'Включите stratum-сервер по адресу %{address} или измените настройки, выбрав %{settings} внизу экрана.' + server_setting: 'Включите stratum-сервер или измените больше настроек, выбрав %{settings} внизу экрана.' info: 'Сервер майнинга запущен, вы можете изменить настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.' - address: IP Адрес - wallet: Адрес кошелька + no_ip_addresses: В вашей системе отсутствуют доступные IP адреса, запуск сервера невозможен, проверьте ваше подключение к сети. + choose_ip_address: 'Выберите IP адрес:' + change_port: 'Измените порт:' + ip_address: IP Адрес + rewards_wallet: Кошелёк для наград server: Stratum-сервер miners: Майнеры devices: Устройства diff --git a/src/gui/views/modal.rs b/src/gui/views/modal.rs index 5f4f411..92755f9 100644 --- a/src/gui/views/modal.rs +++ b/src/gui/views/modal.rs @@ -64,7 +64,7 @@ impl Modal { /// Default width of the content. 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 { Self { 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 { self.position = position; self } - /// Check if [`Modal`] is open. + /// Check if Modal is open. pub fn is_open(&self) -> bool { self.open.load(Ordering::Relaxed) } - /// Mark [`Modal`] closed. + /// Mark Modal closed. pub fn close(&self) { self.open.store(false, Ordering::Relaxed); } - /// Setup possibility to close [`Modal`]. + /// Setup possibility to close Modal. pub fn closeable(self, closeable: bool) -> Self { self.closeable.store(closeable, Ordering::Relaxed); self } - /// Disable possibility to close [`Modal`]. + /// Disable possibility to close Modal. pub fn disable_closing(&self) { self.closeable.store(false, Ordering::Relaxed); } - /// Check if [`Modal`] is closeable. + /// Check if Modal is closeable. pub fn is_closeable(&self) -> bool { self.closeable.load(Ordering::Relaxed) } - /// Set title text. + /// Set title text on Modal creation. pub fn title(mut self, title: String) -> Self { self.title = Some(title.to_uppercase()); 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)) { 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); // Draw line below title. - let line_size = Vec2::new(ui.available_width(), 1.0); - 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::horizontal_line(ui, Colors::STROKE); } } \ No newline at end of file diff --git a/src/gui/views/network.rs b/src/gui/views/network.rs index 145d2b0..79ee999 100644 --- a/src/gui/views/network.rs +++ b/src/gui/views/network.rs @@ -32,7 +32,7 @@ use crate::Settings; pub trait NetworkTab { 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)] @@ -67,7 +67,7 @@ impl Default for 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") .resizable(false) .frame(egui::Frame { @@ -98,7 +98,7 @@ impl Network { .. Default::default() }) .show_inside(ui, |ui| { - self.current_tab.ui(ui); + self.current_tab.ui(ui, cb); }); } diff --git a/src/gui/views/network_metrics.rs b/src/gui/views/network_metrics.rs index ec1272b..e4ce78f 100644 --- a/src/gui/views/network_metrics.rs +++ b/src/gui/views/network_metrics.rs @@ -18,6 +18,7 @@ use grin_servers::DiffBlock; use crate::gui::Colors; 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::node::Node; @@ -33,7 +34,7 @@ impl NetworkTab for NetworkMetrics { 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(); // 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 { diff --git a/src/gui/views/network_mining.rs b/src/gui/views/network_mining.rs index 6d804b0..2e2c56d 100644 --- a/src/gui/views/network_mining.rs +++ b/src/gui/views/network_mining.rs @@ -12,13 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::format; +use std::net::IpAddr; +use std::str::FromStr; use chrono::{DateTime, NaiveDateTime, Utc}; use egui::{RichText, Rounding, ScrollArea, Stroke}; use grin_chain::SyncStatus; use grin_servers::WorkerStats; +use pnet::ipnetwork::IpNetwork; 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::node::Node; use crate::Settings; @@ -31,8 +36,9 @@ impl NetworkTab for NetworkMining { 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(); + // 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 !Node::is_running() { @@ -50,63 +56,158 @@ impl NetworkTab for NetworkMining { return; } - // Stratum mining server address. - let stratum_address = Settings::node_config_to_read() + let stratum_stats = &server_stats.as_ref().unwrap().stratum_stats; + + // Stratum server address + port from config. + let saved_stratum_addr = Settings::node_config_to_read() .members.clone() .server.stratum_mining_config.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; - if !stratum_stats.is_running && !Node::is_stratum_server_starting() { - // Show Stratum setup when mining server is not enabled. - View::center_content(ui, 162.0, |ui| { - let text = t!( - "network_mining.disabled_server", - "address" => stratum_address, - "settings" => FADERS - ); - ui.label(RichText::new(text) + // List of available ip addresses. + 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.ip()); + } + } + } + + // 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) .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; + } + + // 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::>(); + + // 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() { - // Show loading spinner when mining server is starting. ui.centered_and_justified(|ui| { View::big_loading_spinner(ui); }); @@ -118,8 +219,8 @@ impl NetworkTab for NetworkMining { ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { View::rounded_box(ui, - stratum_address, - t!("network_mining.address"), + saved_stratum_addr, + t!("network_mining.ip_address"), [true, false, true, false]); }); columns[1].vertical_centered(|ui| { @@ -131,7 +232,7 @@ impl NetworkTab for NetworkMining { .replace("http://", ""); View::rounded_box(ui, wallet_address, - t!("network_mining.wallet"), + t!("network_mining.rewards_wallet"), [false, true, false, true]); }); }); @@ -198,6 +299,10 @@ impl NetworkTab for NetworkMining { // Show workers stats or info text when possible. let workers_size = stratum_stats.worker_stats.len(); 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() .auto_shrink([false; 2]) .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.horizontal(|ui| { - ui.add_space(6.0); - ui.vertical(|ui| { + ui.horizontal_wrapped(|ui| { + ui.vertical_centered_justified(|ui| { let mut rect = ui.available_rect_before_wrap(); rect.set_height(WORKER_UI_HEIGHT); 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.horizontal_top(|ui| { - let (status_text, status_icon) = match ws.is_connected { - true => { (t!("network_mining.connected"), PLUGS_CONNECTED) } - false => { (t!("network_mining.disconnected"), PLUGS) } + let (status_text, status_icon, status_color) = match ws.is_connected { + true => { (t!("network_mining.connected"), PLUGS_CONNECTED, Colors::BLACK) } + false => { (t!("network_mining.disconnected"), PLUGS, Colors::INACTIVE_TEXT) } }; ui.add_space(5.0); ui.heading(RichText::new(status_icon) - .color(Colors::BLACK) + .color(status_color) .size(18.0)); ui.add_space(2.0); // Draw worker ID. ui.heading(RichText::new(&ws.id) - .color(Colors::BLACK) + .color(status_color) .size(18.0)); ui.add_space(3.0); // Draw worker status. ui.heading(RichText::new(status_text) - .color(Colors::BLACK) + .color(status_color) .size(18.0)); }); ui.horizontal_top(|ui| { @@ -347,7 +451,6 @@ fn draw_worker_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) { .size(16.0)); }); - ui.add_space(2.0); }); }); } \ No newline at end of file diff --git a/src/gui/views/network_node.rs b/src/gui/views/network_node.rs index 1bdfbe5..6b42d70 100644 --- a/src/gui/views/network_node.rs +++ b/src/gui/views/network_node.rs @@ -18,6 +18,7 @@ use grin_servers::PeerStats; use crate::gui::Colors; 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::node::Node; @@ -29,7 +30,7 @@ impl NetworkTab for NetworkNode { 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(); // Show message when node is not running or loading spinner when stats are not available. if !server_stats.is_some() { diff --git a/src/gui/views/network_settings.rs b/src/gui/views/network_settings.rs index 800db3a..30e1691 100644 --- a/src/gui/views/network_settings.rs +++ b/src/gui/views/network_settings.rs @@ -13,6 +13,7 @@ // limitations under the License. use grin_core::global::ChainTypes; +use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{NetworkTab, NetworkTabType}; use crate::Settings; @@ -24,6 +25,6 @@ impl NetworkTab for NetworkSettings { NetworkTabType::Settings } - fn ui(&mut self, ui: &mut egui::Ui) { + fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { } } \ No newline at end of file diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index a2d985f..e9f2c3c 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // 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::text::TextWrapping; use egui::text::{LayoutJob, TextFormat}; 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; @@ -62,17 +62,17 @@ impl View { ui.add_space(4.0); } - /// Temporary button click optimization for touch screens. - fn on_button_click(ui: &mut egui::Ui, resp: Response, action: impl FnOnce()) { + /// Temporary click optimization for touch screens, return `true` if it was clicked. + fn touched(ui: &mut egui::Ui, resp: Response) -> bool { let drag_resp = resp.interact(Sense::click_and_drag()); // Clear pointer event if dragging is out of button area if drag_resp.dragged() && !ui.rect_contains_pointer(drag_resp.rect) { ui.input_mut().pointer = PointerState::default(); } - // Call click action if button is clicked or drag released if drag_resp.drag_released() || drag_resp.clicked() { - (action)(); - }; + return true + } + false } /// Title button with transparent background fill color, contains only icon. @@ -80,13 +80,13 @@ impl View { ui.scope(|ui| { // Disable stroke around title buttons on hover ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE; - let wt = RichText::new(icon.to_string()).size(24.0).color(Colors::TITLE); let br = Button::new(wt) .fill(Colors::TRANSPARENT) .ui(ui); - - Self::on_button_click(ui, br, action); + if Self::touched(ui, br) { + (action)(); + } }); } @@ -96,34 +96,32 @@ impl View { true => { Colors::TITLE } false => { Colors::TEXT } }; - let wt = RichText::new(icon.to_string()).size(24.0).color(text_color); - let stroke = match active { true => { Stroke::NONE } false => { Self::DEFAULT_STROKE } }; - let color = match active { true => { Colors::FILL } 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) .fill(color) .ui(ui); - - Self::on_button_click(ui, br, action); + if Self::touched(ui, br) { + (action)(); + } } /// Draw [`Button`] with specified background fill color. 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(wt) + let br = Button::new(RichText::new(text.to_uppercase()).size(18.0).color(Colors::BUTTON)) .stroke(Self::DEFAULT_STROKE) .fill(fill_color) .ui(ui); - - Self::on_button_click(ui, br, action); + if Self::touched(ui, br) { + (action)(); + } } /// Draw rounded box with some value and label in the middle, @@ -212,31 +210,33 @@ impl View { true => { (format!("{} {}", CHECK_SQUARE, text), Colors::BUTTON) } false => { (format!("{} {}", SQUARE, text), Colors::TEXT) } }; - - let wt = RichText::new(text_value).size(18.0).color(color); - let br = Button::new(wt) + let br = Button::new(RichText::new(text_value).size(18.0).color(color)) .frame(false) .stroke(Stroke::NONE) .fill(Colors::TRANSPARENT) .ui(ui); - - Self::on_button_click(ui, br, callback); + if Self::touched(ui, br) { + (callback)(); + } } - /// Draw the radio button with callback on select. - pub fn radio_button(ui: &mut egui::Ui, selected: bool, text: String, callback: impl FnOnce()) { - let (text_value, color) = match selected { - true => { (format!("{} {}", RADIO_BUTTON, text), Colors::BUTTON) } - false => { (format!("{} {}", CIRCLE, text), Colors::TEXT) } - }; + /// Show a [`RadioButton`]. It is selected if `*current_value == selected_value`. + /// If clicked, `selected_value` is assigned to `*current_value`. + pub fn radio_value(ui: &mut egui::Ui, current: &mut T, value: T, text: String) { + let mut response = ui.radio(*current == value, 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); - let br = Button::new(wt) - .frame(false) - .stroke(Stroke::NONE) - .fill(Colors::TRANSPARENT) - .ui(ui); - - Self::on_button_click(ui, br, callback); + /// Draw horizontal line + pub fn horizontal_line(ui: &mut egui::Ui, color: Color32) { + let line_size = egui::Vec2::new(ui.available_width(), 1.0); + 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), + Stroke { width: 1.0, color }); } } \ No newline at end of file diff --git a/src/node/node.rs b/src/node/node.rs index c1838b3..0a7361d 100644 --- a/src/node/node.rs +++ b/src/node/node.rs @@ -221,13 +221,13 @@ impl Node { }; if Node::is_restarting() { - return t!("sync_status.server_restarting") + return t!("sync_status.node_restarting") } let sync_status = Self::get_sync_status(); if sync_status.is_none() { - return t!("sync_status.server_down") + return t!("sync_status.node_down") } match sync_status.unwrap() {