mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-25 13:40:20 +00:00
feature: mouse support for tabs and dd dialog (#230)
This commit is contained in:
parent
0c21cba189
commit
86c8b474ae
9 changed files with 192 additions and 37 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -35,6 +35,7 @@
|
||||||
"armhf",
|
"armhf",
|
||||||
"armv",
|
"armv",
|
||||||
"atim",
|
"atim",
|
||||||
|
"autohide",
|
||||||
"choco",
|
"choco",
|
||||||
"cmdline",
|
"cmdline",
|
||||||
"commandline",
|
"commandline",
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
"nuget",
|
"nuget",
|
||||||
"nvme",
|
"nvme",
|
||||||
"paren",
|
"paren",
|
||||||
|
"pids",
|
||||||
"pmem",
|
"pmem",
|
||||||
"ppid",
|
"ppid",
|
||||||
"prepush",
|
"prepush",
|
||||||
|
|
|
@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- [#208](https://github.com/ClementTsang/bottom/pull/208): Mouse support for tables and moving to widgets.
|
- [#208](https://github.com/ClementTsang/bottom/pull/208): Mouse support for tables and moving to widgets.
|
||||||
|
|
||||||
- [#217](https://github.com/ClementTsang/bottom/pull/217): Unofficial ARM support.
|
- [#217](https://github.com/ClementTsang/bottom/pull/217): (Kinda) ARM support.
|
||||||
|
|
||||||
- [#220](https://github.com/ClementTsang/bottom/pull/220): Add ability to hide specific temperature and disk entries via config.
|
- [#220](https://github.com/ClementTsang/bottom/pull/220): Add ability to hide specific temperature and disk entries via config.
|
||||||
|
|
||||||
|
|
|
@ -370,10 +370,10 @@ Note that the `and` operator takes precedence over the `or` operator.
|
||||||
|
|
||||||
#### General
|
#### General
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| ------ | ---------------------------------------------------------------------------------------------------------------- |
|
| ------ | ----------------------------------------------------------------------------------------------------- |
|
||||||
| Scroll | Table: Scroll<br>Chart: Zooms in or out by scrolling up or down respectively |
|
| Scroll | Table: Scroll<br>Chart: Zooms in or out by scrolling up or down respectively |
|
||||||
| Click | Selects the clicked widget. For tables, clicking can also select an entry.<br>Can be disabled via options/flags. |
|
| Click | Selects the clicked widget, table entry, dialog option, or tab.<br>Can be disabled via options/flags. |
|
||||||
|
|
||||||
#### CPU bindings
|
#### CPU bindings
|
||||||
|
|
||||||
|
|
50
src/app.rs
50
src/app.rs
|
@ -529,6 +529,7 @@ impl App {
|
||||||
} else {
|
} else {
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
self.delete_dialog_state.is_showing_dd = false;
|
||||||
}
|
}
|
||||||
|
self.is_force_redraw = true;
|
||||||
} else if let BottomWidgetType::ProcSort = self.current_widget.widget_type {
|
} else if let BottomWidgetType::ProcSort = self.current_widget.widget_type {
|
||||||
if let Some(proc_widget_state) = self
|
if let Some(proc_widget_state) = self
|
||||||
.proc_state
|
.proc_state
|
||||||
|
@ -2274,6 +2275,14 @@ impl App {
|
||||||
pub fn left_mouse_click_movement(&mut self, x: u16, y: u16) {
|
pub fn left_mouse_click_movement(&mut self, x: u16, y: u16) {
|
||||||
// Pretty dead simple - iterate through the widget map and go to the widget where the click
|
// Pretty dead simple - iterate through the widget map and go to the widget where the click
|
||||||
// is within.
|
// is within.
|
||||||
|
|
||||||
|
// TODO: [MOUSE] double click functionality...?
|
||||||
|
// TODO: [REFACTOR] might want to refactor this, it's ugly as sin.
|
||||||
|
// TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently everything
|
||||||
|
// is grouped up as an app state. We should separate stuff like event state and gui state and etc.
|
||||||
|
|
||||||
|
// Short circuit if we're in basic table... we might have to handle the basic table arrow
|
||||||
|
// case here...
|
||||||
if let Some(bt) = &mut self.basic_table_widget_state {
|
if let Some(bt) = &mut self.basic_table_widget_state {
|
||||||
if let (
|
if let (
|
||||||
Some((left_tlc_x, left_tlc_y)),
|
Some((left_tlc_x, left_tlc_y)),
|
||||||
|
@ -2330,9 +2339,33 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Second short circuit --- are we in the dd dialog state? If so, only check yes/no and
|
||||||
|
// bail after.
|
||||||
|
if self.is_in_dialog() {
|
||||||
|
if let (
|
||||||
|
Some((yes_tlc_x, yes_tlc_y)),
|
||||||
|
Some((yes_brc_x, yes_brc_y)),
|
||||||
|
Some((no_tlc_x, no_tlc_y)),
|
||||||
|
Some((no_brc_x, no_brc_y)),
|
||||||
|
) = (
|
||||||
|
self.delete_dialog_state.yes_tlc,
|
||||||
|
self.delete_dialog_state.yes_brc,
|
||||||
|
self.delete_dialog_state.no_tlc,
|
||||||
|
self.delete_dialog_state.no_brc,
|
||||||
|
) {
|
||||||
|
if (x >= yes_tlc_x && y >= yes_tlc_y) && (x <= yes_brc_x && y <= yes_brc_y) {
|
||||||
|
self.delete_dialog_state.is_on_yes = true;
|
||||||
|
} else if (x >= no_tlc_x && y >= no_tlc_y) && (x <= no_brc_x && y <= no_brc_y) {
|
||||||
|
self.delete_dialog_state.is_on_yes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut failed_to_get = true;
|
let mut failed_to_get = true;
|
||||||
// TODO: [MOUSE] We could use a better data structure for this? Currently it's a blind
|
// TODO: [MOUSE] We could use a better data structure for this? Currently it's a blind
|
||||||
// traversal through a hashmap, using a 2d binary tree of sorts would be better.
|
// traversal through a hashmap, using a 2d binary tree of sorts would be better.
|
||||||
|
// See: https://docs.rs/kdtree/0.6.0/kdtree/
|
||||||
for (new_widget_id, widget) in &self.widget_map {
|
for (new_widget_id, widget) in &self.widget_map {
|
||||||
if let (Some((tlc_x, tlc_y)), Some((brc_x, brc_y))) =
|
if let (Some((tlc_x, tlc_y)), Some((brc_x, brc_y))) =
|
||||||
(widget.top_left_corner, widget.bottom_right_corner)
|
(widget.top_left_corner, widget.bottom_right_corner)
|
||||||
|
@ -2465,6 +2498,23 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BottomWidgetType::Battery => {
|
||||||
|
if let Some(battery_widget_state) = self
|
||||||
|
.battery_state
|
||||||
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
|
{
|
||||||
|
if let Some(tab_spacing) = &battery_widget_state.tab_click_locs {
|
||||||
|
for (itx, ((tlc_x, tlc_y), (brc_x, brc_y))) in
|
||||||
|
tab_spacing.iter().enumerate()
|
||||||
|
{
|
||||||
|
if (x >= *tlc_x && y >= *tlc_y) && (x <= *brc_x && y <= *brc_y) {
|
||||||
|
battery_widget_state.currently_selected_battery_index = itx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,10 @@ pub struct AppScrollWidgetState {
|
||||||
pub struct AppDeleteDialogState {
|
pub struct AppDeleteDialogState {
|
||||||
pub is_showing_dd: bool,
|
pub is_showing_dd: bool,
|
||||||
pub is_on_yes: bool, // Defaults to "No"
|
pub is_on_yes: bool, // Defaults to "No"
|
||||||
|
pub yes_tlc: Option<(u16, u16)>,
|
||||||
|
pub yes_brc: Option<(u16, u16)>,
|
||||||
|
pub no_tlc: Option<(u16, u16)>,
|
||||||
|
pub no_brc: Option<(u16, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppHelpDialogState {
|
pub struct AppHelpDialogState {
|
||||||
|
@ -768,6 +772,7 @@ pub struct BasicTableWidgetState {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BatteryWidgetState {
|
pub struct BatteryWidgetState {
|
||||||
pub currently_selected_battery_index: usize,
|
pub currently_selected_battery_index: usize,
|
||||||
|
pub tab_click_locs: Option<Vec<((u16, u16), (u16, u16))>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BatteryState {
|
pub struct BatteryState {
|
||||||
|
|
|
@ -214,6 +214,17 @@ impl Painter {
|
||||||
widget.top_left_corner = None;
|
widget.top_left_corner = None;
|
||||||
widget.bottom_right_corner = None;
|
widget.bottom_right_corner = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// And reset dd_dialog...
|
||||||
|
app_state.delete_dialog_state.yes_tlc = None;
|
||||||
|
app_state.delete_dialog_state.yes_brc = None;
|
||||||
|
app_state.delete_dialog_state.no_tlc = None;
|
||||||
|
app_state.delete_dialog_state.no_brc = None;
|
||||||
|
|
||||||
|
// And battery dialog...
|
||||||
|
for battery_widget in app_state.battery_state.widget_states.values_mut() {
|
||||||
|
battery_widget.tab_click_locs = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.autoresize()?;
|
terminal.autoresize()?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
terminal::Frame,
|
terminal::Frame,
|
||||||
widgets::{Block, Borders, Paragraph, Text},
|
widgets::{Block, Borders, Paragraph, Text},
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ pub trait KillDialog {
|
||||||
fn get_dd_spans(&self, app_state: &App) -> Option<Vec<Text<'_>>>;
|
fn get_dd_spans(&self, app_state: &App) -> Option<Vec<Text<'_>>>;
|
||||||
|
|
||||||
fn draw_dd_dialog<B: Backend>(
|
fn draw_dd_dialog<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &App,
|
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &mut App,
|
||||||
draw_loc: Rect,
|
draw_loc: Rect,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
}
|
}
|
||||||
|
@ -34,35 +34,22 @@ impl KillDialog for Painter {
|
||||||
if app_state.is_grouped(app_state.current_widget.widget_id) {
|
if app_state.is_grouped(app_state.current_widget.widget_id) {
|
||||||
if to_kill_processes.1.len() != 1 {
|
if to_kill_processes.1.len() != 1 {
|
||||||
Text::raw(format!(
|
Text::raw(format!(
|
||||||
"Kill {} processes with the name \"{}\"?",
|
"Kill {} processes with the name \"{}\"? Press ENTER to confirm.",
|
||||||
to_kill_processes.1.len(),
|
to_kill_processes.1.len(),
|
||||||
to_kill_processes.0
|
to_kill_processes.0
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Text::raw(format!(
|
Text::raw(format!(
|
||||||
"Kill 1 process with the name \"{}\"?",
|
"Kill 1 process with the name \"{}\"? Press ENTER to confirm.",
|
||||||
to_kill_processes.0
|
to_kill_processes.0
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text::raw(format!(
|
Text::raw(format!(
|
||||||
"Kill process \"{}\" with PID {}?",
|
"Kill process \"{}\" with PID {}? Press ENTER to confirm.",
|
||||||
to_kill_processes.0, first_pid
|
to_kill_processes.0, first_pid
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
Text::raw("\n\n"),
|
|
||||||
if app_state.delete_dialog_state.is_on_yes {
|
|
||||||
Text::styled("Yes", self.colours.currently_selected_text_style)
|
|
||||||
} else {
|
|
||||||
Text::raw("Yes")
|
|
||||||
},
|
|
||||||
Text::raw(" "),
|
|
||||||
if app_state.delete_dialog_state.is_on_yes {
|
|
||||||
Text::raw("No")
|
|
||||||
} else {
|
|
||||||
Text::styled("No", self.colours.currently_selected_text_style)
|
|
||||||
},
|
|
||||||
Text::raw("\n"),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +58,7 @@ impl KillDialog for Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_dd_dialog<B: Backend>(
|
fn draw_dd_dialog<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &App,
|
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &mut App,
|
||||||
draw_loc: Rect,
|
draw_loc: Rect,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(dd_text) = dd_text {
|
if let Some(dd_text) = dd_text {
|
||||||
|
@ -131,6 +118,75 @@ impl KillDialog for Painter {
|
||||||
draw_loc,
|
draw_loc,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Now draw buttons if needed...
|
||||||
|
let split_draw_loc = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(
|
||||||
|
if app_state.dd_err.is_some() {
|
||||||
|
vec![Constraint::Percentage(100)]
|
||||||
|
} else {
|
||||||
|
vec![Constraint::Min(0), Constraint::Length(3)]
|
||||||
|
}
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
// This being true implies that dd_err is none.
|
||||||
|
if let Some(button_draw_loc) = split_draw_loc.get(1) {
|
||||||
|
let (yes_button, no_button) = if app_state.delete_dialog_state.is_on_yes {
|
||||||
|
(
|
||||||
|
Text::styled("Yes", self.colours.currently_selected_text_style),
|
||||||
|
Text::raw("No"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
Text::raw("Yes"),
|
||||||
|
Text::styled("No", self.colours.currently_selected_text_style),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let button_layout = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Percentage(35),
|
||||||
|
Constraint::Percentage(30),
|
||||||
|
Constraint::Percentage(35),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(*button_draw_loc);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new([yes_button].iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.alignment(Alignment::Right),
|
||||||
|
button_layout[0],
|
||||||
|
);
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new([no_button].iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.alignment(Alignment::Left),
|
||||||
|
button_layout[2],
|
||||||
|
);
|
||||||
|
|
||||||
|
if app_state.should_get_widget_bounds() {
|
||||||
|
app_state.delete_dialog_state.yes_tlc =
|
||||||
|
Some((button_layout[0].x, button_layout[0].y));
|
||||||
|
app_state.delete_dialog_state.yes_brc = Some((
|
||||||
|
button_layout[0].x + button_layout[0].width,
|
||||||
|
button_layout[0].y + button_layout[0].height,
|
||||||
|
));
|
||||||
|
|
||||||
|
app_state.delete_dialog_state.no_tlc =
|
||||||
|
Some((button_layout[2].x, button_layout[2].y));
|
||||||
|
app_state.delete_dialog_state.no_brc = Some((
|
||||||
|
button_layout[2].x + button_layout[2].width,
|
||||||
|
button_layout[2].y + button_layout[2].height,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if app_state.dd_err.is_some() {
|
if app_state.dd_err.is_some() {
|
||||||
return app_state.delete_dialog_state.is_showing_dd;
|
return app_state.delete_dialog_state.is_showing_dd;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,6 +23,7 @@ impl BatteryDisplayWidget for Painter {
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
||||||
widget_id: u64,
|
widget_id: u64,
|
||||||
) {
|
) {
|
||||||
|
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
||||||
if let Some(battery_widget_state) =
|
if let Some(battery_widget_state) =
|
||||||
app_state.battery_state.widget_states.get_mut(&widget_id)
|
app_state.battery_state.widget_states.get_mut(&widget_id)
|
||||||
{
|
{
|
||||||
|
@ -115,6 +116,18 @@ impl BatteryDisplayWidget for Painter {
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.split(draw_loc)[0];
|
.split(draw_loc)[0];
|
||||||
|
|
||||||
|
let tab_draw_loc = Layout::default()
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Min(0),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.split(draw_loc)[1];
|
||||||
|
|
||||||
if let Some(battery_details) = app_state
|
if let Some(battery_details) = app_state
|
||||||
.canvas_data
|
.canvas_data
|
||||||
.battery_data
|
.battery_data
|
||||||
|
@ -188,6 +201,13 @@ impl BatteryDisplayWidget for Painter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let battery_names = app_state
|
||||||
|
.canvas_data
|
||||||
|
.battery_data
|
||||||
|
.iter()
|
||||||
|
.map(|battery| &battery.battery_name)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Has to be placed AFTER for tui 0.9, place BEFORE for 0.10.
|
// Has to be placed AFTER for tui 0.9, place BEFORE for 0.10.
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
// Tabs::new(
|
// Tabs::new(
|
||||||
|
@ -198,26 +218,36 @@ impl BatteryDisplayWidget for Painter {
|
||||||
// .map(|battery| Spans::from(battery.battery_name.clone())))
|
// .map(|battery| Spans::from(battery.battery_name.clone())))
|
||||||
// .collect::<Vec<_>>(),
|
// .collect::<Vec<_>>(),
|
||||||
// )
|
// )
|
||||||
// FIXME: [MOUSE] Support mouse for the tabs?
|
|
||||||
Tabs::default()
|
Tabs::default()
|
||||||
.titles(
|
.titles(battery_names.as_ref())
|
||||||
app_state
|
.block(Block::default())
|
||||||
.canvas_data
|
|
||||||
.battery_data
|
|
||||||
.iter()
|
|
||||||
.map(|battery| &battery.battery_name)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.block(battery_block)
|
|
||||||
.divider(tui::symbols::line::VERTICAL)
|
.divider(tui::symbols::line::VERTICAL)
|
||||||
.style(self.colours.text_style)
|
.style(self.colours.text_style)
|
||||||
.highlight_style(self.colours.currently_selected_text_style)
|
.highlight_style(self.colours.currently_selected_text_style)
|
||||||
.select(battery_widget_state.currently_selected_battery_index),
|
.select(battery_widget_state.currently_selected_battery_index),
|
||||||
draw_loc,
|
tab_draw_loc,
|
||||||
);
|
);
|
||||||
|
|
||||||
if app_state.should_get_widget_bounds() {
|
if should_get_widget_bounds {
|
||||||
|
// Tab wizardry
|
||||||
|
if !battery_names.is_empty() {
|
||||||
|
let mut current_x = tab_draw_loc.x;
|
||||||
|
let current_y = tab_draw_loc.y;
|
||||||
|
let mut tab_click_locs: Vec<((u16, u16), (u16, u16))> = vec![];
|
||||||
|
for battery in battery_names {
|
||||||
|
// +1 because there's a space after the tab label.
|
||||||
|
let width =
|
||||||
|
unicode_width::UnicodeWidthStr::width(battery.as_str()) as u16 + 1;
|
||||||
|
tab_click_locs
|
||||||
|
.push(((current_x, current_y), (current_x + width, current_y)));
|
||||||
|
|
||||||
|
// +2 because we want to go one space past to get to the '|', then one MORE
|
||||||
|
// to start at the blank space before the tab label.
|
||||||
|
current_x = current_x + width + 2;
|
||||||
|
}
|
||||||
|
battery_widget_state.tab_click_locs = Some(tab_click_locs);
|
||||||
|
}
|
||||||
|
|
||||||
// Update draw loc in widget map
|
// Update draw loc in widget map
|
||||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||||
|
|
|
@ -37,6 +37,7 @@ lazy_static! {
|
||||||
tui::style::Style::default().fg(tui::style::Color::LightBlue);
|
tui::style::Style::default().fg(tui::style::Color::LightBlue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: [HELP] I wanna update this before release... it's missing mouse too.
|
||||||
// Help text
|
// Help text
|
||||||
pub const HELP_CONTENTS_TEXT: [&str; 8] = [
|
pub const HELP_CONTENTS_TEXT: [&str; 8] = [
|
||||||
"Press the corresponding numbers to jump to the section, or scroll:\n",
|
"Press the corresponding numbers to jump to the section, or scroll:\n",
|
||||||
|
|
Loading…
Reference in a new issue