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
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

View file

@ -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: Устройства

View file

@ -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);
}
}

View file

@ -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);
});
}

View file

@ -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 {

View file

@ -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::<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() {
// 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);
});
});
}

View file

@ -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() {

View file

@ -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) {
}
}

View file

@ -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<T: PartialEq>(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 });
}
}

View file

@ -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() {