build: migrate to tui-realm 4.0

Upgrade tuirealm (3.x -> 4.0.0), tui-realm-stdlib (3 -> 4), tui-term
(0.2 -> 0.3). Apply all breaking changes from the 4.0 migration guide
across the termscp UI.

Key changes:

- Root-level re-exports removed; imports moved to module-qualified
  paths (`tuirealm::application`, `::component`, `::event`, `::props`,
  `::state`, `::subscription`, `::listener`, `::ratatui`). Same for
  stdlib component types (`tui_realm_stdlib::components::*`).
- `MockComponent` trait renamed to `Component`; old `Component` trait
  renamed to `AppComponent`. `#[derive(MockComponent)]` is now
  `#[derive(Component)]`. `Component::on` now takes `&Event<_>`.
- `TextSpan` replaced with `SpanStatic`/`LineStatic`/`TextStatic`
  (ratatui-based); tuple `(String, Alignment)` titles replaced with
  the new `Title` builder; `Alignment` split into
  `HorizontalAlignment`/`VerticalAlignment`; stdlib components use
  `.alignment_horizontal` instead of `.alignment`.
- `State::One`/`PropPayload::One` -> `Single`. `CmdResult::None`
  -> `NoChange`. `Props::get_or` removed; `Props::get` now returns a
  borrowed `Option<&AttrValue>` (call sites switched to
  `.and_then(AttrValue::as_*)`). `Component::query` returns
  `Option<QueryResult<'a>>`.
- `Attribute::HighlightedColor` -> `HighlightStyle` (a full `Style`).
  `.highlighted_*` helpers renamed to `.highlight_*`.
- `PollStrategy::UpTo(n)` now requires a `Duration`; tick timeout moved
  from `EventListenerCfg::poll_timeout` into `PollStrategy`.
- `TerminalBridge` removed; `Context` now holds
  `CrosstermTerminalAdapter` directly and enables raw mode + alternate
  screen explicitly. The `TerminalAdapter` trait is imported where its
  methods are used.
- `Update` trait removed; activity `update` methods are plain inherent
  functions.
- `ProgressBar` replaced by stdlib `Gauge`. Paragraph `.wrap` renamed
  to `.wrap_trim`; `.text` now takes an `Into<Text>`. Stdlib `List` row
  items are now individual lines (`Vec<Span>` per row) rather than a
  `Table` of spans; custom `FileList`/`Log` convert between the two
  models.
- Radio builders drop `.foreground(color)` so unselected items render
  with the terminal default foreground, and set
  `highlight_style(Style::default().fg(color).add_modifier(REVERSED))`
  so the selected entry is visibly highlighted only with the theme
  color.
- Custom `FileList` keeps the selected row highlighted with the full
  highlight style when focused and falls back to a foreground-only
  style when unfocused.
- Theme loading is now backwards compatible: `Theme` uses a custom
  `Deserialize` through an intermediate `ThemeFile` with optional
  fields, so missing keys, unknown values or legacy aliases
  (`transfer_progress_bar_full`/`_partial`) fall back to defaults on a
  per-field basis instead of failing the whole load.
This commit is contained in:
Christian Visintin
2026-04-19 01:54:46 +05:30
parent 9160c52cb0
commit 6252df2959
78 changed files with 2127 additions and 1588 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use remotefs::File;
use remotefs::fs::UnixPex;
use tuirealm::{State, StateValue};
use tuirealm::state::{State, StateValue};
use super::browser::FileExplorerTab;
use super::lib::browser::FoundExplorerTab;
@@ -135,7 +135,7 @@ impl FileTransferActivity {
fn get_selected_index(&self, id: &Id) -> SelectedFileIndex {
match self.app.state(id) {
Ok(State::One(StateValue::Usize(idx))) => SelectedFileIndex::One(idx),
Ok(State::Single(StateValue::Usize(idx))) => SelectedFileIndex::One(idx),
_ => SelectedFileIndex::None,
}
}
@@ -9,6 +9,7 @@ use std::time::SystemTime;
use remotefs::File;
use remotefs::fs::Metadata;
use tuirealm::terminal::TerminalAdapter;
use super::super::ui_result;
use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload};
@@ -6,6 +6,8 @@
// ext
use std::path::{Path, PathBuf};
use tuirealm::terminal::TerminalAdapter;
use super::{File, FileTransferActivity, LogLevel, TransferPayload};
impl FileTransferActivity {
@@ -3,7 +3,7 @@
//! this little module exposes the routine to create a pending action on the file transfer activity.
//! A pending action is an action which blocks the execution of the application in await of a certain `Msg`.
use tuirealm::{PollStrategy, Update};
use tuirealm::application::PollStrategy;
use super::{FileTransferActivity, Msg};
@@ -18,7 +18,10 @@ impl FileTransferActivity {
self.redraw = true;
loop {
// Poll
match self.app.tick(PollStrategy::Once) {
match self
.app
.tick(PollStrategy::Once(std::time::Duration::from_millis(10)))
{
Ok(mut messages) => {
if !messages.is_empty() {
self.redraw = true;
+6 -6
View File
@@ -2,9 +2,9 @@
//!
//! file transfer activity components
use tui_realm_stdlib::Phantom;
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers};
use tuirealm::{Component, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::Phantom;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use super::{Msg, TransferMsg, UiMsg};
@@ -30,13 +30,13 @@ pub use self::log::Log;
pub use self::selected_files::SelectedFilesList;
pub use self::terminal::Terminal;
#[derive(Default, MockComponent)]
#[derive(Default, Component)]
pub struct GlobalListener {
component: Phantom,
}
impl Component<Msg, NoUserEvent> for GlobalListener {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for GlobalListener {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
@@ -3,10 +3,14 @@
//! log tab component
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Style, Table};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{
AttrValue, Attribute, Borders, Color, HorizontalAlignment, Props, QueryResult, Style, Table,
Title,
};
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
use tuirealm::state::{State, StateValue};
use super::{Msg, UiMsg};
@@ -31,38 +35,47 @@ impl Log {
}
}
impl MockComponent for Log {
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
let width: usize = area.width as usize - 4;
impl Component for Log {
fn view(
&mut self,
frame: &mut tuirealm::ratatui::Frame,
area: tuirealm::ratatui::layout::Rect,
) {
let _width: usize = area.width as usize - 4;
let focus = self
.props
.get_or(Attribute::Focus, AttrValue::Flag(false))
.unwrap_flag();
.get(Attribute::Focus)
.and_then(AttrValue::as_flag)
.unwrap_or(false);
let borders = self
.props
.get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
.unwrap_borders();
.get(Attribute::Borders)
.and_then(AttrValue::as_borders)
.unwrap_or_default();
let bg = self
.props
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
.unwrap_color();
.get(Attribute::Background)
.and_then(AttrValue::as_color)
.unwrap_or(Color::Reset);
// Make list
let list_items: Vec<ListItem> = self
.props
.get(Attribute::Content)
.unwrap()
.unwrap_table()
.iter()
.map(|row| {
let row_refs = row.iter().collect::<Vec<_>>();
ListItem::new(tui_realm_stdlib::utils::wrap_spans(
row_refs.as_slice(),
width,
&self.props,
))
.and_then(AttrValue::as_table)
.map(|table| {
table
.iter()
.map(|row| {
let spans = row
.iter()
.flat_map(|line| line.spans.iter().cloned())
.collect::<Vec<_>>();
ListItem::new(tuirealm::ratatui::text::Line::from(spans))
})
.collect()
})
.collect();
let title = ("Log".to_string(), Alignment::Left);
.unwrap_or_default();
let title = Title::from("Log".to_string()).alignment(HorizontalAlignment::Left);
let w = TuiList::new(list_items)
.block(tui_realm_stdlib::utils::get_block(
borders,
@@ -79,29 +92,26 @@ impl MockComponent for Log {
frame.render_stateful_widget(w, area, &mut state);
}
fn query(&self, attr: Attribute) -> Option<AttrValue> {
self.props.get(attr)
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
self.props.get_for_query(attr)
}
fn attr(&mut self, attr: Attribute, value: AttrValue) {
self.props.set(attr, value);
if matches!(attr, Attribute::Content) {
self.states.set_list_len(
match self
.props
.get(Attribute::Content)
.map(AttrValue::unwrap_table)
{
Some(spans) => spans.len(),
_ => 0,
},
);
let len = self
.props
.get(Attribute::Content)
.and_then(AttrValue::as_table)
.map(std::vec::Vec::len)
.unwrap_or(0);
self.states.set_list_len(len);
self.states.reset_list_index();
}
}
fn state(&self) -> State {
State::One(StateValue::Usize(self.states.get_list_index()))
State::Single(StateValue::Usize(self.states.get_list_index()))
}
fn perform(&mut self, cmd: Cmd) -> CmdResult {
@@ -112,7 +122,7 @@ impl MockComponent for Log {
if prev != self.states.get_list_index() {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Move(Direction::Up) => {
@@ -121,7 +131,7 @@ impl MockComponent for Log {
if prev != self.states.get_list_index() {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Scroll(Direction::Down) => {
@@ -130,7 +140,7 @@ impl MockComponent for Log {
if prev != self.states.get_list_index() {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Scroll(Direction::Up) => {
@@ -139,7 +149,7 @@ impl MockComponent for Log {
if prev != self.states.get_list_index() {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::GoTo(Position::Begin) => {
@@ -148,7 +158,7 @@ impl MockComponent for Log {
if prev != self.states.get_list_index() {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::GoTo(Position::End) => {
@@ -157,16 +167,16 @@ impl MockComponent for Log {
if prev != self.states.get_list_index() {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
_ => CmdResult::None,
_ => CmdResult::NoChange,
}
}
}
impl Component<Msg, NoUserEvent> for Log {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for Log {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
self.perform(Cmd::Move(Direction::Down));
@@ -2,13 +2,15 @@
//!
//! file transfer activity components
use tui_realm_stdlib::Span;
use tuirealm::props::{Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::Span;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, NoUserEvent};
use tuirealm::props::{Color, SpanStatic};
use tuirealm::ratatui::style::Stylize;
use super::Msg;
#[derive(MockComponent)]
#[derive(Component)]
pub struct FooterBar {
component: Span,
}
@@ -17,37 +19,37 @@ impl FooterBar {
pub fn new(key_color: Color) -> Self {
Self {
component: Span::default().spans([
TextSpan::from("<F1|H>").bold().fg(key_color),
TextSpan::from(" Help "),
TextSpan::from("<TAB>").bold().fg(key_color),
TextSpan::from(" Change tab "),
TextSpan::from("<SPACE>").bold().fg(key_color),
TextSpan::from(" Transfer "),
TextSpan::from("<ENTER>").bold().fg(key_color),
TextSpan::from(" Enter dir "),
TextSpan::from("<F2|S>").bold().fg(key_color),
TextSpan::from(" Save as "),
TextSpan::from("<F3|V>").bold().fg(key_color),
TextSpan::from(" View "),
TextSpan::from("<F4|O>").bold().fg(key_color),
TextSpan::from(" Edit "),
TextSpan::from("<F5|C>").bold().fg(key_color),
TextSpan::from(" Copy "),
TextSpan::from("<F6|R>").bold().fg(key_color),
TextSpan::from(" Rename "),
TextSpan::from("<F7|D>").bold().fg(key_color),
TextSpan::from(" Make dir "),
TextSpan::from("<F8|DEL>").bold().fg(key_color),
TextSpan::from(" Delete "),
TextSpan::from("<F10|Q>").bold().fg(key_color),
TextSpan::from(" Quit "),
SpanStatic::from("<F1|H>").bold().fg(key_color),
SpanStatic::from(" Help "),
SpanStatic::from("<TAB>").bold().fg(key_color),
SpanStatic::from(" Change tab "),
SpanStatic::from("<SPACE>").bold().fg(key_color),
SpanStatic::from(" Transfer "),
SpanStatic::from("<ENTER>").bold().fg(key_color),
SpanStatic::from(" Enter dir "),
SpanStatic::from("<F2|S>").bold().fg(key_color),
SpanStatic::from(" Save as "),
SpanStatic::from("<F3|V>").bold().fg(key_color),
SpanStatic::from(" View "),
SpanStatic::from("<F4|O>").bold().fg(key_color),
SpanStatic::from(" Edit "),
SpanStatic::from("<F5|C>").bold().fg(key_color),
SpanStatic::from(" Copy "),
SpanStatic::from("<F6|R>").bold().fg(key_color),
SpanStatic::from(" Rename "),
SpanStatic::from("<F7|D>").bold().fg(key_color),
SpanStatic::from(" Make dir "),
SpanStatic::from("<F8|DEL>").bold().fg(key_color),
SpanStatic::from(" Delete "),
SpanStatic::from("<F10|Q>").bold().fg(key_color),
SpanStatic::from(" Quit "),
]),
}
}
}
impl Component<Msg, NoUserEvent> for FooterBar {
fn on(&mut self, _: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for FooterBar {
fn on(&mut self, _: &Event<NoUserEvent>) -> Option<Msg> {
None
}
}
@@ -1,10 +1,13 @@
use remotefs::fs::{UnixPex, UnixPexClass};
use tui_realm_stdlib::Checkbox;
use tui_realm_stdlib::components::Checkbox;
use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, AttrValue, Attribute, BorderSides, Borders, Color};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{
AttrValue, Attribute, BorderSides, Borders, Color, HorizontalAlignment, Props, Title,
};
use tuirealm::ratatui::layout::{Constraint, Direction as LayoutDirection, Layout};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
@@ -60,21 +63,21 @@ impl ChmodPopup {
user: Checkbox::default()
.foreground(color)
.choices(["Read", "Write", "Execute"])
.title("User", Alignment::Left)
.title(Title::from("User").alignment(HorizontalAlignment::Left))
.borders(Borders::default().sides(BorderSides::NONE))
.values(&make_pex_values(pex.user()))
.rewind(true),
group: Checkbox::default()
.foreground(color)
.choices(["Read", "Write", "Execute"])
.title("Group", Alignment::Left)
.title(Title::from("Group").alignment(HorizontalAlignment::Left))
.borders(Borders::default().sides(BorderSides::NONE))
.values(&make_pex_values(pex.group()))
.rewind(true),
others: Checkbox::default()
.foreground(color)
.choices(["Read", "Write", "Execute"])
.title("Others", Alignment::Left)
.title(Title::from("Others").alignment(HorizontalAlignment::Left))
.borders(Borders::default().sides(BorderSides::NONE))
.values(&make_pex_values(pex.others()))
.rewind(true),
@@ -146,8 +149,8 @@ impl ChmodPopup {
}
}
impl MockComponent for ChmodPopup {
fn attr(&mut self, attr: tuirealm::Attribute, value: AttrValue) {
impl Component for ChmodPopup {
fn attr(&mut self, attr: tuirealm::props::Attribute, value: AttrValue) {
self.props.set(attr, value.clone());
if attr == Attribute::Focus {
@@ -166,28 +169,40 @@ impl MockComponent for ChmodPopup {
}
Cmd::Move(Direction::Up) => {
self.active_checkbox_up();
CmdResult::None
CmdResult::NoChange
}
Cmd::Move(Direction::Down) => {
self.active_checkbox_down();
CmdResult::None
CmdResult::NoChange
}
Cmd::Toggle => self.get_active_checkbox().perform(cmd),
Cmd::Submit => CmdResult::Submit(self.state()),
_ => CmdResult::None,
_ => CmdResult::NoChange,
}
}
fn query(&self, attr: tuirealm::Attribute) -> Option<AttrValue> {
self.props.get(attr)
fn query<'a>(
&'a self,
attr: tuirealm::props::Attribute,
) -> Option<tuirealm::props::QueryResult<'a>> {
self.props.get_for_query(attr)
}
fn state(&self) -> State {
State::One(StateValue::U32(self.get_mode().into()))
State::Single(StateValue::U32(self.get_mode().into()))
}
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) != AttrValue::Flag(true) {
fn view(
&mut self,
frame: &mut tuirealm::ratatui::Frame,
area: tuirealm::ratatui::layout::Rect,
) {
if !self
.props
.get(Attribute::Display)
.and_then(AttrValue::as_flag)
.unwrap_or(true)
{
return;
}
let chunks = Layout::default()
@@ -205,10 +220,11 @@ impl MockComponent for ChmodPopup {
let focus = self
.props
.get_or(Attribute::Focus, AttrValue::Flag(false))
.unwrap_flag();
.get(Attribute::Focus)
.and_then(AttrValue::as_flag)
.unwrap_or(false);
let div_title = (self.title.clone(), Alignment::Center);
let div_title = Title::from(self.title.clone()).alignment(HorizontalAlignment::Center);
let div = tui_realm_stdlib::utils::get_block(
Borders::default().color(self.color),
@@ -225,8 +241,8 @@ impl MockComponent for ChmodPopup {
}
}
impl Component<Msg, NoUserEvent> for ChmodPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ChmodPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseChmodPopup))
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct CopyPopup {
component: Input,
}
@@ -22,17 +23,19 @@ impl CopyPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"destination",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Copy file(s) to\u{2026}", Alignment::Center),
))
.title(
Title::from("Copy file(s) to\u{2026}").alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for CopyPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for CopyPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -73,13 +76,13 @@ impl Component<Msg, NoUserEvent> for CopyPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => {
State::Single(StateValue::String(i)) => {
Some(Msg::Transfer(TransferMsg::CopyFileTo(i)))
}
_ => Some(Msg::None),
@@ -1,12 +1,15 @@
use tui_realm_stdlib::Radio;
use tui_realm_stdlib::components::Radio;
use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct DeletePopup {
component: Radio,
}
@@ -15,21 +18,25 @@ impl DeletePopup {
pub fn new(color: Color) -> Self {
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Yes", "No"])
.value(1)
.title("Delete file(s)?", Alignment::Center),
.title(Title::from("Delete file(s)?").alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for DeletePopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for DeletePopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -59,7 +66,7 @@ impl Component<Msg, NoUserEvent> for DeletePopup {
}) => {
if matches!(
self.perform(Cmd::Submit),
CmdResult::Submit(State::One(StateValue::Usize(0)))
CmdResult::Submit(State::Single(StateValue::Usize(0)))
) {
Some(Msg::Transfer(TransferMsg::DeleteFile))
} else {
@@ -1,12 +1,15 @@
use tui_realm_stdlib::Radio;
use tui_realm_stdlib::components::Radio;
use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct DisconnectPopup {
component: Radio,
}
@@ -15,20 +18,27 @@ impl DisconnectPopup {
pub fn new(color: Color) -> Self {
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Yes", "No"])
.title("Are you sure you want to disconnect?", Alignment::Center),
.title(
Title::from("Are you sure you want to disconnect?")
.alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for DisconnectPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for DisconnectPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -58,7 +68,7 @@ impl Component<Msg, NoUserEvent> for DisconnectPopup {
}) => {
if matches!(
self.perform(Cmd::Submit),
CmdResult::Submit(State::One(StateValue::Usize(0)))
CmdResult::Submit(State::Single(StateValue::Usize(0)))
) {
Some(Msg::Ui(UiMsg::Disconnect))
} else {
@@ -1,11 +1,12 @@
use tui_realm_stdlib::Paragraph;
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::Paragraph;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, SpanStatic};
use tuirealm::ratatui::text::Text;
use crate::ui::activities::filetransfer::{Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct ErrorPopup {
component: Paragraph,
}
@@ -14,21 +15,23 @@ impl ErrorPopup {
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
Self {
component: Paragraph::default()
.alignment(Alignment::Center)
.alignment_horizontal(HorizontalAlignment::Center)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.text([TextSpan::from(text.as_ref())])
.wrap(true),
.text(Text::from_iter([SpanStatic::from(
text.as_ref().to_string(),
)]))
.wrap_trim(true),
}
}
}
impl Component<Msg, NoUserEvent> for ErrorPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ErrorPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Esc | Key::Enter,
@@ -39,7 +42,7 @@ impl Component<Msg, NoUserEvent> for ErrorPopup {
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct FatalPopup {
component: Paragraph,
}
@@ -48,21 +51,23 @@ impl FatalPopup {
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
Self {
component: Paragraph::default()
.alignment(Alignment::Center)
.alignment_horizontal(HorizontalAlignment::Center)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.text([TextSpan::from(text.as_ref())])
.wrap(true),
.text(Text::from_iter([SpanStatic::from(
text.as_ref().to_string(),
)]))
.wrap_trim(true),
}
}
}
impl Component<Msg, NoUserEvent> for FatalPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for FatalPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Esc | Key::Enter,
@@ -2,17 +2,20 @@ use std::time::UNIX_EPOCH;
use bytesize::ByteSize;
use remotefs::File;
use tui_realm_stdlib::List;
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::List;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, TableBuilder, Title,
};
use tuirealm::ratatui::style::Stylize;
#[cfg(posix)]
use uzers::{get_group_by_gid, get_user_by_uid};
use crate::ui::activities::filetransfer::{Msg, UiMsg};
use crate::utils::fmt::fmt_time;
#[derive(MockComponent)]
#[derive(Component)]
pub struct FileInfoPopup {
component: List,
}
@@ -28,23 +31,23 @@ impl FileInfoPopup {
};
// Make texts
texts
.add_col(TextSpan::from("Path: "))
.add_col(TextSpan::new(path.as_str()).fg(Color::Yellow));
.add_col(SpanStatic::from("Path: "))
.add_col(SpanStatic::raw(path.clone()).fg(Color::Yellow));
texts
.add_row()
.add_col(TextSpan::from("Name: "))
.add_col(TextSpan::new(file.name()).fg(Color::Yellow));
.add_col(SpanStatic::from("Name: "))
.add_col(SpanStatic::raw(file.name().clone()).fg(Color::Yellow));
if let Some(filetype) = file.extension() {
texts
.add_row()
.add_col(TextSpan::from("File type: "))
.add_col(TextSpan::new(filetype).fg(Color::LightGreen));
.add_col(SpanStatic::from("File type: "))
.add_col(SpanStatic::raw(filetype.clone()).fg(Color::LightGreen));
}
let (bsize, size): (ByteSize, u64) = (ByteSize(file.metadata().size), file.metadata().size);
texts
.add_row()
.add_col(TextSpan::from("Size: "))
.add_col(TextSpan::new(format!("{bsize} ({size})").as_str()).fg(Color::Cyan));
.add_col(SpanStatic::from("Size: "))
.add_col(SpanStatic::raw(format!("{bsize} ({size})")).fg(Color::Cyan));
let atime: String = fmt_time(
file.metadata().accessed.unwrap_or(UNIX_EPOCH),
"%b %d %Y %H:%M:%S",
@@ -59,16 +62,16 @@ impl FileInfoPopup {
);
texts
.add_row()
.add_col(TextSpan::from("Creation time: "))
.add_col(TextSpan::new(ctime.as_str()).fg(Color::LightGreen));
.add_col(SpanStatic::from("Creation time: "))
.add_col(SpanStatic::raw(ctime.clone()).fg(Color::LightGreen));
texts
.add_row()
.add_col(TextSpan::from("Last modified time: "))
.add_col(TextSpan::new(mtime.as_str()).fg(Color::LightBlue));
.add_col(SpanStatic::from("Last modified time: "))
.add_col(SpanStatic::raw(mtime.clone()).fg(Color::LightBlue));
texts
.add_row()
.add_col(TextSpan::from("Last access time: "))
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
.add_col(SpanStatic::from("Last access time: "))
.add_col(SpanStatic::raw(atime.clone()).fg(Color::LightRed));
// User
#[cfg(posix)]
let username: String = match file.metadata().uid {
@@ -93,24 +96,29 @@ impl FileInfoPopup {
let group: String = format!("{}", file.metadata().gid.unwrap_or(0));
texts
.add_row()
.add_col(TextSpan::from("User: "))
.add_col(TextSpan::new(username.as_str()).fg(Color::LightYellow));
.add_col(SpanStatic::from("User: "))
.add_col(SpanStatic::raw(username.clone()).fg(Color::LightYellow));
texts
.add_row()
.add_col(TextSpan::from("Group: "))
.add_col(TextSpan::new(group.as_str()).fg(Color::Blue));
.add_col(SpanStatic::from("Group: "))
.add_col(SpanStatic::raw(group.clone()).fg(Color::Blue));
Self {
component: List::default()
.borders(Borders::default().modifiers(BorderType::Rounded))
.scroll(false)
.title(file.name(), Alignment::Left)
.rows(texts.build()),
.title(Title::from(file.name().clone()).alignment(HorizontalAlignment::Left))
.rows(
texts
.build()
.into_iter()
.map(|row| row.into_iter().flat_map(|l| l.spans).collect::<Vec<_>>()),
),
}
}
}
impl Component<Msg, NoUserEvent> for FileInfoPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for FileInfoPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Esc | Key::Enter,
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct FilterPopup {
component: Input,
}
@@ -22,20 +23,20 @@ impl FilterPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"regex or wildmatch",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
))
.title(
"Filter files by regex or wildmatch in the current directory",
Alignment::Center,
Title::from("Filter files by regex or wildmatch in the current directory")
.alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for FilterPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for FilterPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -76,13 +77,15 @@ impl Component<Msg, NoUserEvent> for FilterPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(filter)) => Some(Msg::Ui(UiMsg::FilterFiles(filter))),
State::Single(StateValue::String(filter)) => {
Some(Msg::Ui(UiMsg::FilterFiles(filter)))
}
_ => Some(Msg::None),
},
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
@@ -1,12 +1,14 @@
use std::path::{Path, PathBuf};
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, PropValue, Style};
use tuirealm::{
AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent, State, StateValue,
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{
AttrValue, Attribute, BorderType, Borders, Color, HorizontalAlignment, InputType, PropValue,
Style, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
@@ -51,14 +53,15 @@ impl From<CmdResult> for Suggestion {
fn from(value: CmdResult) -> Self {
match value {
CmdResult::Batch(v) if v.len() == 1 => {
if let CmdResult::Submit(State::One(StateValue::String(s))) = v.first().unwrap() {
if let CmdResult::Submit(State::Single(StateValue::String(s))) = v.first().unwrap()
{
Suggestion::Suggest(s.clone())
} else {
Suggestion::None
}
}
CmdResult::Batch(v) if v.len() == 2 => {
if let CmdResult::Submit(State::One(StateValue::String(s))) = v.get(1).unwrap() {
if let CmdResult::Submit(State::Single(StateValue::String(s))) = v.get(1).unwrap() {
Suggestion::Rescan(PathBuf::from(s))
} else {
Suggestion::None
@@ -72,13 +75,13 @@ impl From<CmdResult> for Suggestion {
impl From<Suggestion> for CmdResult {
fn from(value: Suggestion) -> Self {
match value {
Suggestion::None => CmdResult::None,
Suggestion::Suggest(s) => {
CmdResult::Batch(vec![CmdResult::Submit(State::One(StateValue::String(s)))])
}
Suggestion::None => CmdResult::NoChange,
Suggestion::Suggest(s) => CmdResult::Batch(vec![CmdResult::Submit(State::Single(
StateValue::String(s),
))]),
Suggestion::Rescan(p) => CmdResult::Batch(vec![
CmdResult::None,
CmdResult::Submit(State::One(StateValue::String(
CmdResult::NoChange,
CmdResult::Submit(State::Single(StateValue::String(
p.to_string_lossy().to_string(),
))),
]),
@@ -187,18 +190,25 @@ impl GotoPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"/foo/bar/buzz",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Go to… (Press <TAB> for autocompletion)", Alignment::Center),
))
.title(
Title::from("Go to… (Press <TAB> for autocompletion)")
.alignment(HorizontalAlignment::Center),
),
states,
}
}
}
impl MockComponent for GotoPopup {
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::prelude::Rect) {
impl Component for GotoPopup {
fn view(
&mut self,
frame: &mut tuirealm::ratatui::Frame,
area: tuirealm::ratatui::prelude::Rect,
) {
self.input.view(frame, area);
}
@@ -220,12 +230,12 @@ impl MockComponent for GotoPopup {
}
}
fn query(&self, attr: Attribute) -> Option<AttrValue> {
fn query<'a>(&'a self, attr: Attribute) -> Option<tuirealm::props::QueryResult<'a>> {
self.input.query(attr)
}
fn state(&self) -> State {
State::One(StateValue::String(self.states.computed_search()))
State::Single(StateValue::String(self.states.computed_search()))
}
fn perform(&mut self, cmd: Cmd) -> CmdResult {
@@ -236,7 +246,7 @@ impl MockComponent for GotoPopup {
.search
.as_ref()
.cloned()
.unwrap_or_else(|| self.input.state().unwrap_one().unwrap_string());
.unwrap_or_else(|| self.input.state().unwrap_single().unwrap_string());
let suggest = self.states.suggest(&input);
if let Suggestion::Suggest(suggestion) = suggest.clone() {
self.input
@@ -247,7 +257,7 @@ impl MockComponent for GotoPopup {
}
cmd => {
let res = self.input.perform(cmd);
if let CmdResult::Changed(State::One(StateValue::String(new_text))) = &res {
if let CmdResult::Changed(State::Single(StateValue::String(new_text))) = &res {
self.states.search = Some(new_text.clone());
}
res
@@ -256,8 +266,8 @@ impl MockComponent for GotoPopup {
}
}
impl Component<Msg, NoUserEvent> for GotoPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for GotoPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -298,7 +308,7 @@ impl Component<Msg, NoUserEvent> for GotoPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
@@ -311,7 +321,7 @@ impl Component<Msg, NoUserEvent> for GotoPopup {
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::GoTo(i))),
State::Single(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::GoTo(i))),
_ => Some(Msg::None),
},
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
@@ -1,12 +1,15 @@
use tui_realm_stdlib::List;
use tui_realm_stdlib::components::List;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, TableBuilder, Title,
};
use tuirealm::ratatui::style::Stylize;
use crate::ui::activities::filetransfer::{Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct KeybindingsPopup {
component: List,
}
@@ -18,148 +21,152 @@ impl KeybindingsPopup {
.borders(Borders::default().modifiers(BorderType::Rounded))
.scroll(true)
.step(8)
.highlighted_str("? ")
.title("Keybindings", Alignment::Center)
.highlight_str("? ")
.title(Title::from("Keybindings").alignment(HorizontalAlignment::Center))
.rewind(true)
.rows(
TableBuilder::default()
.add_col(TextSpan::new("<ESC>").bold().fg(key_color))
.add_col(TextSpan::from(" Disconnect"))
.add_col(SpanStatic::raw("<ESC>").bold().fg(key_color))
.add_col(SpanStatic::from(" Disconnect"))
.add_row()
.add_col(TextSpan::new("<BACKSPACE>").bold().fg(key_color))
.add_col(TextSpan::from(" Go to previous directory"))
.add_col(SpanStatic::raw("<BACKSPACE>").bold().fg(key_color))
.add_col(SpanStatic::from(" Go to previous directory"))
.add_row()
.add_col(TextSpan::new("<TAB|RIGHT|LEFT>").bold().fg(key_color))
.add_col(TextSpan::from(" Change explorer tab"))
.add_col(SpanStatic::raw("<TAB|RIGHT|LEFT>").bold().fg(key_color))
.add_col(SpanStatic::from(" Change explorer tab"))
.add_row()
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(key_color))
.add_col(TextSpan::from(" Move up/down in list"))
.add_col(SpanStatic::raw("<UP/DOWN>").bold().fg(key_color))
.add_col(SpanStatic::from(" Move up/down in list"))
.add_row()
.add_col(TextSpan::new("<ENTER>").bold().fg(key_color))
.add_col(TextSpan::from(" Enter directory"))
.add_col(SpanStatic::raw("<ENTER>").bold().fg(key_color))
.add_col(SpanStatic::from(" Enter directory"))
.add_row()
.add_col(TextSpan::new("<SPACE>").bold().fg(key_color))
.add_col(TextSpan::from(" Upload/Download file"))
.add_col(SpanStatic::raw("<SPACE>").bold().fg(key_color))
.add_col(SpanStatic::from(" Upload/Download file"))
.add_row()
.add_col(TextSpan::new("<BACKTAB>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<BACKTAB>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Switch between explorer and log window",
))
.add_row()
.add_col(TextSpan::new("<A>").bold().fg(key_color))
.add_col(TextSpan::from(" Toggle hidden files"))
.add_col(SpanStatic::raw("<A>").bold().fg(key_color))
.add_col(SpanStatic::from(" Toggle hidden files"))
.add_row()
.add_col(TextSpan::new("<B>").bold().fg(key_color))
.add_col(TextSpan::from(" Change file sorting mode"))
.add_col(SpanStatic::raw("<B>").bold().fg(key_color))
.add_col(SpanStatic::from(" Change file sorting mode"))
.add_row()
.add_col(TextSpan::new("<C|F5>").bold().fg(key_color))
.add_col(TextSpan::from(" Copy"))
.add_col(SpanStatic::raw("<C|F5>").bold().fg(key_color))
.add_col(SpanStatic::from(" Copy"))
.add_row()
.add_col(TextSpan::new("<D|F7>").bold().fg(key_color))
.add_col(TextSpan::from(" Make directory"))
.add_col(SpanStatic::raw("<D|F7>").bold().fg(key_color))
.add_col(SpanStatic::from(" Make directory"))
.add_row()
.add_col(TextSpan::new("<F>").bold().fg(key_color))
.add_col(TextSpan::from(" Search files"))
.add_col(SpanStatic::raw("<F>").bold().fg(key_color))
.add_col(SpanStatic::from(" Search files"))
.add_row()
.add_col(TextSpan::new("<G>").bold().fg(key_color))
.add_col(TextSpan::from(" Go to path"))
.add_col(SpanStatic::raw("<G>").bold().fg(key_color))
.add_col(SpanStatic::from(" Go to path"))
.add_row()
.add_col(TextSpan::new("<H|F1>").bold().fg(key_color))
.add_col(TextSpan::from(" Show help"))
.add_col(SpanStatic::raw("<H|F1>").bold().fg(key_color))
.add_col(SpanStatic::from(" Show help"))
.add_row()
.add_col(TextSpan::new("<I>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<I>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Show info about selected file",
))
.add_row()
.add_col(TextSpan::new("<K>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<K>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Create symlink pointing to the current selected entry",
))
.add_row()
.add_col(TextSpan::new("<L>").bold().fg(key_color))
.add_col(TextSpan::from(" Reload directory content"))
.add_col(SpanStatic::raw("<L>").bold().fg(key_color))
.add_col(SpanStatic::from(" Reload directory content"))
.add_row()
.add_col(TextSpan::new("<M>").bold().fg(key_color))
.add_col(TextSpan::from(" Select file"))
.add_col(SpanStatic::raw("<M>").bold().fg(key_color))
.add_col(SpanStatic::from(" Select file"))
.add_row()
.add_col(TextSpan::new("<N>").bold().fg(key_color))
.add_col(TextSpan::from(" Create new file"))
.add_col(SpanStatic::raw("<N>").bold().fg(key_color))
.add_col(SpanStatic::from(" Create new file"))
.add_row()
.add_col(TextSpan::new("<O|F4>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<O|F4>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Open text file with preferred editor",
))
.add_row()
.add_col(TextSpan::new("<P>").bold().fg(key_color))
.add_col(TextSpan::from(" Toggle bottom panel"))
.add_col(SpanStatic::raw("<P>").bold().fg(key_color))
.add_col(SpanStatic::from(" Toggle bottom panel"))
.add_row()
.add_col(TextSpan::new("<Q|F10>").bold().fg(key_color))
.add_col(TextSpan::from(" Quit termscp"))
.add_col(SpanStatic::raw("<Q|F10>").bold().fg(key_color))
.add_col(SpanStatic::from(" Quit termscp"))
.add_row()
.add_col(TextSpan::new("<R|F6>").bold().fg(key_color))
.add_col(TextSpan::from(" Rename file"))
.add_col(SpanStatic::raw("<R|F6>").bold().fg(key_color))
.add_col(SpanStatic::from(" Rename file"))
.add_row()
.add_col(TextSpan::new("<S|F2>").bold().fg(key_color))
.add_col(TextSpan::from(" Save file as"))
.add_col(SpanStatic::raw("<S|F2>").bold().fg(key_color))
.add_col(SpanStatic::from(" Save file as"))
.add_row()
.add_col(TextSpan::new("<T>").bold().fg(key_color))
.add_col(TextSpan::from(" Watch/unwatch file changes"))
.add_col(SpanStatic::raw("<T>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Watch/unwatch file changes",
))
.add_row()
.add_col(TextSpan::new("<U>").bold().fg(key_color))
.add_col(TextSpan::from(" Go to parent directory"))
.add_col(SpanStatic::raw("<U>").bold().fg(key_color))
.add_col(SpanStatic::from(" Go to parent directory"))
.add_row()
.add_col(TextSpan::new("<V|F3>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<V|F3>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Open file with default application for file type",
))
.add_row()
.add_col(TextSpan::new("<W>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<W>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Open file with specified application",
))
.add_row()
.add_col(TextSpan::new("<X>").bold().fg(key_color))
.add_col(TextSpan::from(" Execute shell command"))
.add_col(SpanStatic::raw("<X>").bold().fg(key_color))
.add_col(SpanStatic::from(" Execute shell command"))
.add_row()
.add_col(TextSpan::new("<Y>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<Y>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Toggle synchronized browsing",
))
.add_row()
.add_col(TextSpan::new("<Z>").bold().fg(key_color))
.add_col(TextSpan::from(" Change file permissions"))
.add_col(SpanStatic::raw("<Z>").bold().fg(key_color))
.add_col(SpanStatic::from(" Change file permissions"))
.add_row()
.add_col(TextSpan::new("</>").bold().fg(key_color))
.add_col(TextSpan::from(" Filter files"))
.add_col(SpanStatic::raw("</>").bold().fg(key_color))
.add_col(SpanStatic::from(" Filter files"))
.add_row()
.add_col(TextSpan::new("<DEL|F8|E>").bold().fg(key_color))
.add_col(TextSpan::from(" Delete selected file"))
.add_col(SpanStatic::raw("<DEL|F8|E>").bold().fg(key_color))
.add_col(SpanStatic::from(" Delete selected file"))
.add_row()
.add_col(TextSpan::new("<CTRL+A>").bold().fg(key_color))
.add_col(TextSpan::from(" Select all files"))
.add_col(SpanStatic::raw("<CTRL+A>").bold().fg(key_color))
.add_col(SpanStatic::from(" Select all files"))
.add_row()
.add_col(TextSpan::new("<ALT+A>").bold().fg(key_color))
.add_col(TextSpan::from(" Deselect all files"))
.add_col(SpanStatic::raw("<ALT+A>").bold().fg(key_color))
.add_col(SpanStatic::from(" Deselect all files"))
.add_row()
.add_col(TextSpan::new("<CTRL+C>").bold().fg(key_color))
.add_col(TextSpan::from(" Interrupt file transfer"))
.add_col(SpanStatic::raw("<CTRL+C>").bold().fg(key_color))
.add_col(SpanStatic::from(" Interrupt file transfer"))
.add_row()
.add_col(TextSpan::new("<CTRL+S>").bold().fg(key_color))
.add_col(TextSpan::from(
.add_col(SpanStatic::raw("<CTRL+S>").bold().fg(key_color))
.add_col(SpanStatic::from(
" Get total path size of selected files",
))
.add_row()
.add_col(TextSpan::new("<CTRL+T>").bold().fg(key_color))
.add_col(TextSpan::from(" Show watched paths"))
.build(),
.add_col(SpanStatic::raw("<CTRL+T>").bold().fg(key_color))
.add_col(SpanStatic::from(" Show watched paths"))
.build()
.into_iter()
.map(|row| row.into_iter().flat_map(|l| l.spans).collect::<Vec<_>>()),
),
}
}
}
impl Component<Msg, NoUserEvent> for KeybindingsPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for KeybindingsPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Esc | Key::Enter,
@@ -1,12 +1,15 @@
use tui_realm_stdlib::{Input, Radio};
use tui_realm_stdlib::components::{Input, Radio};
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, InputType, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, PendingActionMsg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct MkdirPopup {
component: Input,
}
@@ -22,17 +25,17 @@ impl MkdirPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"New directory name",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("directory-name", Alignment::Center),
))
.title(Title::from("directory-name").alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for MkdirPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for MkdirPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -73,13 +76,13 @@ impl Component<Msg, NoUserEvent> for MkdirPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::Mkdir(i))),
State::Single(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::Mkdir(i))),
_ => Some(Msg::None),
},
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
@@ -90,7 +93,7 @@ impl Component<Msg, NoUserEvent> for MkdirPopup {
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct SyncBrowsingMkdirPopup {
component: Radio,
}
@@ -99,25 +102,25 @@ impl SyncBrowsingMkdirPopup {
pub fn new(color: Color, dir_name: &str) -> Self {
Self {
component: Radio::default()
.highlight_style(Style::default().fg(color).add_modifier(TextModifiers::REVERSED))
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Yes", "No"])
.choices(["Yes", "No"])
.title(
format!(
Title::from(format!(
r#"Sync browsing: directory "{dir_name}" doesn't exist. Do you want to create it?"#
),
Alignment::Center,
))
.alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -149,7 +152,7 @@ impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
}) => {
if matches!(
self.perform(Cmd::Submit),
CmdResult::Submit(State::One(StateValue::Usize(0)))
CmdResult::Submit(State::Single(StateValue::Usize(0)))
) {
Some(Msg::PendingAction(PendingActionMsg::MakePendingDirectory))
} else {
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct NewfilePopup {
component: Input,
}
@@ -22,17 +23,17 @@ impl NewfilePopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"New file name",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("file.txt", Alignment::Center),
))
.title(Title::from("file.txt").alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for NewfilePopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for NewfilePopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -73,13 +74,15 @@ impl Component<Msg, NoUserEvent> for NewfilePopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::NewFile(i))),
State::Single(StateValue::String(i)) => {
Some(Msg::Transfer(TransferMsg::NewFile(i)))
}
_ => Some(Msg::None),
},
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct OpenWithPopup {
component: Input,
}
@@ -22,17 +23,20 @@ impl OpenWithPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"Open file with\u{2026}",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Type the program to open the file with", Alignment::Center),
))
.title(
Title::from("Type the program to open the file with")
.alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for OpenWithPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for OpenWithPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -73,13 +77,13 @@ impl Component<Msg, NoUserEvent> for OpenWithPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => {
State::Single(StateValue::String(i)) => {
Some(Msg::Transfer(TransferMsg::OpenFileWith(i)))
}
_ => Some(Msg::None),
@@ -1,30 +1,30 @@
use tui_realm_stdlib::ProgressBar;
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::Gauge;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, Title};
use crate::ui::activities::filetransfer::{Msg, TransferMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct TransferProgressBar {
component: ProgressBar,
component: Gauge,
}
impl TransferProgressBar {
pub fn new<S: Into<String>>(prog: f64, label: S, title: S, color: Color) -> Self {
Self {
component: ProgressBar::default()
component: Gauge::default()
.borders(Borders::default().modifiers(BorderType::Rounded))
.foreground(color)
.label(label)
.progress(prog)
.title(title, Alignment::Center),
.title(Title::from(title.into()).alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for TransferProgressBar {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for TransferProgressBar {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
if matches!(
ev,
Event::Keyboard(KeyEvent {
@@ -1,12 +1,15 @@
use tui_realm_stdlib::Radio;
use tui_realm_stdlib::components::Radio;
use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct QuitPopup {
component: Radio,
}
@@ -15,20 +18,27 @@ impl QuitPopup {
pub fn new(color: Color) -> Self {
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Yes", "No"])
.title("Are you sure you want to quit termscp?", Alignment::Center),
.title(
Title::from("Are you sure you want to quit termscp?")
.alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for QuitPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for QuitPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -58,7 +68,7 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
}) => {
if matches!(
self.perform(Cmd::Submit),
CmdResult::Submit(State::One(StateValue::Usize(0)))
CmdResult::Submit(State::Single(StateValue::Usize(0)))
) {
Some(Msg::Ui(UiMsg::Quit))
} else {
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct RenamePopup {
component: Input,
}
@@ -22,17 +23,19 @@ impl RenamePopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"/foo/bar/buzz.txt",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Move file(s) to\u{2026}", Alignment::Center),
))
.title(
Title::from("Move file(s) to\u{2026}").alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for RenamePopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for RenamePopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -73,13 +76,13 @@ impl Component<Msg, NoUserEvent> for RenamePopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => {
State::Single(StateValue::String(i)) => {
Some(Msg::Transfer(TransferMsg::RenameFile(i)))
}
_ => Some(Msg::None),
@@ -1,12 +1,15 @@
use tui_realm_stdlib::Radio;
use tui_realm_stdlib::components::Radio;
use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, PendingActionMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct ReplacePopup {
component: Radio,
}
@@ -19,20 +22,24 @@ impl ReplacePopup {
};
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Replace", "Skip", "Replace All", "Skip All", "Cancel"])
.title(text, Alignment::Center),
.title(Title::from(text).alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for ReplacePopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ReplacePopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -60,19 +67,19 @@ impl Component<Msg, NoUserEvent> for ReplacePopup {
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.perform(Cmd::Submit) {
CmdResult::Submit(State::One(StateValue::Usize(0))) => {
CmdResult::Submit(State::Single(StateValue::Usize(0))) => {
Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwrite))
}
CmdResult::Submit(State::One(StateValue::Usize(1))) => {
CmdResult::Submit(State::Single(StateValue::Usize(1))) => {
Some(Msg::PendingAction(PendingActionMsg::ReplaceSkip))
}
CmdResult::Submit(State::One(StateValue::Usize(2))) => {
CmdResult::Submit(State::Single(StateValue::Usize(2))) => {
Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll))
}
CmdResult::Submit(State::One(StateValue::Usize(3))) => {
CmdResult::Submit(State::Single(StateValue::Usize(3))) => {
Some(Msg::PendingAction(PendingActionMsg::ReplaceSkipAll))
}
CmdResult::Submit(State::One(StateValue::Usize(4))) => {
CmdResult::Submit(State::Single(StateValue::Usize(4))) => {
Some(Msg::PendingAction(PendingActionMsg::ReplaceCancel))
}
_ => Some(Msg::None),
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct SaveAsPopup {
component: Input,
}
@@ -22,17 +23,17 @@ impl SaveAsPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"/foo/bar/buzz.txt",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("Save as\u{2026}", Alignment::Center),
))
.title(Title::from("Save as\u{2026}").alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for SaveAsPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for SaveAsPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -73,13 +74,13 @@ impl Component<Msg, NoUserEvent> for SaveAsPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => {
State::Single(StateValue::String(i)) => {
Some(Msg::Transfer(TransferMsg::SaveFileAs(i)))
}
_ => Some(Msg::None),
@@ -1,13 +1,16 @@
use tui_realm_stdlib::Radio;
use tui_realm_stdlib::components::Radio;
use tuirealm::command::{Cmd, CmdResult, Direction};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::explorer::FileSorting;
use crate::ui::activities::filetransfer::{Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct SortingPopup {
component: Radio,
}
@@ -16,14 +19,18 @@ impl SortingPopup {
pub fn new(value: FileSorting, color: Color) -> Self {
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Name", "Modify time", "Creation time", "Size"])
.title("Sort files by\u{2026}", Alignment::Center)
.title(Title::from("Sort files by\u{2026}").alignment(HorizontalAlignment::Center))
.value(match value {
FileSorting::CreationTime => 2,
FileSorting::ModifyTime => 1,
@@ -35,8 +42,8 @@ impl SortingPopup {
}
}
impl Component<Msg, NoUserEvent> for SortingPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for SortingPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
let result = match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -50,7 +57,7 @@ impl Component<Msg, NoUserEvent> for SortingPopup {
}) => return Some(Msg::Ui(UiMsg::CloseFileSortingPopup)),
_ => return None,
};
if let CmdResult::Changed(State::One(StateValue::Usize(i))) = result {
if let CmdResult::Changed(State::Single(StateValue::Usize(i))) = result {
Some(Msg::Ui(UiMsg::ChangeFileSorting(match i {
0 => FileSorting::Name,
1 => FileSorting::ModifyTime,
@@ -1,12 +1,14 @@
use tui_realm_stdlib::Span;
use tuirealm::props::{Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::Span;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, NoUserEvent};
use tuirealm::props::{Color, SpanStatic};
use tuirealm::ratatui::style::Stylize;
use crate::explorer::FileSorting;
use crate::ui::activities::filetransfer::Msg;
use crate::ui::activities::filetransfer::lib::browser::Browser;
#[derive(MockComponent)]
#[derive(Component)]
pub struct StatusBarLocal {
component: Span,
}
@@ -17,22 +19,22 @@ impl StatusBarLocal {
let hidden_files = hidden_files_label(browser.host_bridge().hidden_files_visible());
Self {
component: Span::default().spans([
TextSpan::new("File sorting: ").fg(sorting_color),
TextSpan::new(file_sorting).fg(sorting_color).reversed(),
TextSpan::new(" Hidden files: ").fg(hidden_color),
TextSpan::new(hidden_files).fg(hidden_color).reversed(),
SpanStatic::raw("File sorting: ").fg(sorting_color),
SpanStatic::raw(file_sorting).fg(sorting_color).reversed(),
SpanStatic::raw(" Hidden files: ").fg(hidden_color),
SpanStatic::raw(hidden_files).fg(hidden_color).reversed(),
]),
}
}
}
impl Component<Msg, NoUserEvent> for StatusBarLocal {
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for StatusBarLocal {
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
None
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct StatusBarRemote {
component: Span,
}
@@ -52,19 +54,19 @@ impl StatusBarRemote {
};
Self {
component: Span::default().spans([
TextSpan::new("File sorting: ").fg(sorting_color),
TextSpan::new(file_sorting).fg(sorting_color).reversed(),
TextSpan::new(" Hidden files: ").fg(hidden_color),
TextSpan::new(hidden_files).fg(hidden_color).reversed(),
TextSpan::new(" Sync browsing: ").fg(sync_color),
TextSpan::new(sync_browsing).fg(sync_color).reversed(),
SpanStatic::raw("File sorting: ").fg(sorting_color),
SpanStatic::raw(file_sorting).fg(sorting_color).reversed(),
SpanStatic::raw(" Hidden files: ").fg(hidden_color),
SpanStatic::raw(hidden_files).fg(hidden_color).reversed(),
SpanStatic::raw(" Sync browsing: ").fg(sync_color),
SpanStatic::raw(sync_browsing).fg(sync_color).reversed(),
]),
}
}
}
impl Component<Msg, NoUserEvent> for StatusBarRemote {
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for StatusBarRemote {
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
None
}
}
@@ -1,12 +1,13 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct SymlinkPopup {
component: Input,
}
@@ -22,20 +23,20 @@ impl SymlinkPopup {
)
.foreground(color)
.input_type(InputType::Text)
.placeholder(
.placeholder(tuirealm::props::SpanStatic::styled(
"Symlink name",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
))
.title(
"Create a symlink pointing to the selected entry",
Alignment::Center,
Title::from("Create a symlink pointing to the selected entry")
.alignment(HorizontalAlignment::Center),
),
}
}
}
impl Component<Msg, NoUserEvent> for SymlinkPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for SymlinkPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -76,13 +77,13 @@ impl Component<Msg, NoUserEvent> for SymlinkPopup {
code: Key::Char(ch),
..
}) => {
self.perform(Cmd::Type(ch));
self.perform(Cmd::Type(*ch));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => match self.state() {
State::One(StateValue::String(i)) => {
State::Single(StateValue::String(i)) => {
Some(Msg::Transfer(TransferMsg::CreateSymlink(i)))
}
_ => Some(Msg::None),
@@ -1,11 +1,12 @@
use tui_realm_stdlib::Paragraph;
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
use tui_realm_stdlib::components::Paragraph;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, SpanStatic};
use tuirealm::ratatui::text::Text;
use crate::ui::activities::filetransfer::{Msg, TransferMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct WaitPopup {
component: Paragraph,
}
@@ -14,26 +15,28 @@ impl WaitPopup {
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
Self {
component: Paragraph::default()
.alignment(Alignment::Center)
.alignment_horizontal(HorizontalAlignment::Center)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.text([TextSpan::from(text.as_ref())])
.wrap(true),
.text(Text::from_iter([SpanStatic::from(
text.as_ref().to_string(),
)]))
.wrap_trim(true),
}
}
}
impl Component<Msg, NoUserEvent> for WaitPopup {
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for WaitPopup {
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
None
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct WalkdirWaitPopup {
component: Paragraph,
}
@@ -42,24 +45,24 @@ impl WalkdirWaitPopup {
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
Self {
component: Paragraph::default()
.alignment(Alignment::Center)
.alignment_horizontal(HorizontalAlignment::Center)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.text([
TextSpan::from(text.as_ref()),
TextSpan::from("Press 'CTRL+C' to abort"),
])
.wrap(true),
.text(Text::from_iter([
SpanStatic::from(text.as_ref().to_string()),
SpanStatic::from("Press 'CTRL+C' to abort"),
]))
.wrap_trim(true),
}
}
}
impl Component<Msg, NoUserEvent> for WalkdirWaitPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for WalkdirWaitPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
if matches!(
ev,
Event::Keyboard(KeyEvent {
@@ -1,12 +1,15 @@
use tui_realm_stdlib::{List, Radio};
use tui_realm_stdlib::components::{List, Radio};
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, TextModifiers, Title,
};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct WatchedPathsList {
component: List,
}
@@ -23,24 +26,24 @@ impl WatchedPathsList {
.rewind(true)
.scroll(true)
.step(4)
.highlighted_color(color)
.highlighted_str("\u{27a4} ")
.highlight_style(Style::default().fg(color))
.highlight_str("\u{27a4} ")
.title(
"These files are currently synched with the remote host",
Alignment::Center,
Title::from("These files are currently synched with the remote host")
.alignment(HorizontalAlignment::Center),
)
.rows(
paths
.iter()
.map(|x| vec![TextSpan::from(x.to_string_lossy().to_string())])
.collect(),
.map(|x| vec![SpanStatic::from(x.to_string_lossy().to_string())])
.collect::<Vec<Vec<SpanStatic>>>(),
),
}
}
}
impl Component<Msg, NoUserEvent> for WatchedPathsList {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for WatchedPathsList {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseWatchedPathsList))
@@ -82,7 +85,7 @@ impl Component<Msg, NoUserEvent> for WatchedPathsList {
code: Key::Enter, ..
}) => {
// get state
if let State::One(StateValue::Usize(idx)) = self.component.state() {
if let State::Single(StateValue::Usize(idx)) = self.component.state() {
Some(Msg::Transfer(TransferMsg::ToggleWatchFor(idx)))
} else {
Some(Msg::None)
@@ -93,7 +96,7 @@ impl Component<Msg, NoUserEvent> for WatchedPathsList {
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct WatcherPopup {
component: Radio,
}
@@ -106,20 +109,24 @@ impl WatcherPopup {
};
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.choices(["Yes", "No"])
.title(text, Alignment::Center),
.title(Title::from(text).alignment(HorizontalAlignment::Center)),
}
}
}
impl Component<Msg, NoUserEvent> for WatcherPopup {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for WatcherPopup {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -149,7 +156,7 @@ impl Component<Msg, NoUserEvent> for WatcherPopup {
}) => {
if matches!(
self.perform(Cmd::Submit),
CmdResult::Submit(State::One(StateValue::Usize(0)))
CmdResult::Submit(State::Single(StateValue::Usize(0)))
) {
Some(Msg::Transfer(TransferMsg::ToggleWatch))
} else {
@@ -1,14 +1,15 @@
use std::path::{Path, PathBuf};
use tui_realm_stdlib::List;
use tui_realm_stdlib::components::List;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, Title};
use tuirealm::state::{State, StateValue};
use crate::ui::activities::filetransfer::{MarkQueue, Msg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct SelectedFilesList {
component: List,
paths: Vec<PathBuf>,
@@ -35,20 +36,20 @@ impl SelectedFilesList {
.rewind(true)
.scroll(true)
.step(4)
.highlighted_color(color)
.highlighted_str("➤ ")
.title(title, Alignment::Left)
.highlight_style(Style::default().fg(color))
.highlight_str("➤ ")
.title(Title::from(title.to_string()).alignment(HorizontalAlignment::Left))
.rows(
paths
.iter()
.map(|(src, dest)| {
vec![
TextSpan::from(Self::filename(src)),
TextSpan::from(" -> "),
TextSpan::from(Self::filename(dest)),
SpanStatic::from(Self::filename(src)),
SpanStatic::from(" -> "),
SpanStatic::from(Self::filename(dest)),
]
})
.collect(),
.collect::<Vec<Vec<SpanStatic>>>(),
),
}
}
@@ -61,8 +62,8 @@ impl SelectedFilesList {
}
}
impl Component<Msg, NoUserEvent> for SelectedFilesList {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for SelectedFilesList {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Down, ..
@@ -112,7 +113,7 @@ impl Component<Msg, NoUserEvent> for SelectedFilesList {
..
}) => {
// unmark the selected file
let State::One(StateValue::Usize(idx)) = self.state() else {
let State::Single(StateValue::Usize(idx)) = self.state() else {
return None;
};
@@ -3,16 +3,16 @@ mod history;
mod line;
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent};
use tuirealm::props::Color;
use tuirealm::{AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
use tuirealm::props::{AttrValue, Attribute, Color};
use self::component::TerminalComponent;
use self::line::Line;
use super::Msg;
use crate::ui::activities::filetransfer::{TransferMsg, UiMsg};
#[derive(MockComponent, Default)]
#[derive(Component, Default)]
pub struct Terminal {
component: TerminalComponent,
}
@@ -45,8 +45,8 @@ impl Terminal {
}
}
impl Component<Msg, NoUserEvent> for Terminal {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for Terminal {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Msg::Ui(UiMsg::CloseExecPopup))
@@ -55,7 +55,7 @@ impl Component<Msg, NoUserEvent> for Terminal {
code: Key::Enter, ..
}) => match self.component.perform(Cmd::Submit) {
CmdResult::Submit(state) => {
let cmd = state.unwrap_one().unwrap_string();
let cmd = state.unwrap_single().unwrap_string();
Some(Msg::Transfer(TransferMsg::ExecuteCmd(cmd)))
}
_ => None,
@@ -127,7 +127,7 @@ impl Component<Msg, NoUserEvent> for Terminal {
Event::Keyboard(KeyEvent {
code: Key::Char(c), ..
}) => {
self.component.perform(Cmd::Type(c));
self.component.perform(Cmd::Type(*c));
Some(Msg::None)
}
_ => None,
@@ -1,10 +1,11 @@
use tui_term::vt100::Parser;
use tui_term::widget::PseudoTerminal;
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::props::{BorderSides, BorderType, Style};
use tuirealm::component::Component;
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Props, QueryResult, Style};
use tuirealm::ratatui::layout::Rect;
use tuirealm::ratatui::widgets::Block;
use tuirealm::{AttrValue, Attribute, MockComponent, Props, State, StateValue};
use tuirealm::state::{State, StateValue};
use super::Line;
use super::history::History;
@@ -45,8 +46,7 @@ impl TerminalComponent {
}
pub fn write_prompt(&mut self) {
if let Some(value) = self.query(Attribute::Content) {
let prompt = value.unwrap_string();
if let Some(AttrValue::String(prompt)) = self.props.get(Attribute::Content).cloned() {
self.parser.process(prompt.as_bytes());
}
}
@@ -83,8 +83,8 @@ impl TerminalComponent {
}
}
impl MockComponent for TerminalComponent {
fn view(&mut self, frame: &mut tuirealm::Frame, area: Rect) {
impl Component for TerminalComponent {
fn view(&mut self, frame: &mut tuirealm::ratatui::Frame, area: Rect) {
let width = area.width.saturating_sub(2);
let height = area.height.saturating_sub(2);
@@ -95,23 +95,30 @@ impl MockComponent for TerminalComponent {
}
let title = self
.query(Attribute::Title)
.map(AttrValue::unwrap_string)
.props
.get(Attribute::Title)
.and_then(|v| match v {
AttrValue::String(s) => Some(s.clone()),
_ => None,
})
.unwrap_or_else(|| "Terminal".to_string());
let fg = self
.query(Attribute::Foreground)
.map(AttrValue::unwrap_color)
.props
.get(Attribute::Foreground)
.and_then(AttrValue::as_color)
.unwrap_or(tuirealm::ratatui::style::Color::Reset);
let bg = self
.query(Attribute::Background)
.map(AttrValue::unwrap_color)
.props
.get(Attribute::Background)
.and_then(AttrValue::as_color)
.unwrap_or(tuirealm::ratatui::style::Color::Reset);
let border_color = self
.query(Attribute::Borders)
.map(AttrValue::unwrap_color)
.props
.get(Attribute::Borders)
.and_then(AttrValue::as_color)
.unwrap_or(tuirealm::ratatui::style::Color::Reset);
let terminal = PseudoTerminal::new(self.parser.screen())
@@ -128,13 +135,13 @@ impl MockComponent for TerminalComponent {
frame.render_widget(terminal, area);
}
fn query(&self, attr: tuirealm::Attribute) -> Option<tuirealm::AttrValue> {
self.props.get(attr)
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
self.props.get_for_query(attr)
}
fn attr(&mut self, attr: tuirealm::Attribute, value: AttrValue) {
fn attr(&mut self, attr: Attribute, value: AttrValue) {
if attr == Attribute::Text {
if let tuirealm::AttrValue::String(s) = value {
if let AttrValue::String(s) = value {
self.parser.process(b"\r");
self.parser.process(s.as_bytes());
self.parser.process(b"\r");
@@ -146,14 +153,14 @@ impl MockComponent for TerminalComponent {
}
fn state(&self) -> State {
State::One(StateValue::String(self.line.content().to_string()))
State::Single(StateValue::String(self.line.content().to_string()))
}
fn perform(&mut self, cmd: Cmd) -> CmdResult {
match cmd {
Cmd::Type(s) => {
if !s.is_ascii() || self.scroll > 0 {
return CmdResult::None; // Ignore non-ASCII characters or if scrolled
return CmdResult::NoChange; // Ignore non-ASCII characters or if scrolled
}
self.parser.process(&[s as u8]);
self.line.push(s);
@@ -161,46 +168,46 @@ impl MockComponent for TerminalComponent {
}
Cmd::Move(Direction::Down) => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move down if not scrolled
return CmdResult::NoChange; // Cannot move down if not scrolled
}
self.history_next();
CmdResult::None
CmdResult::NoChange
}
Cmd::Move(Direction::Left) => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move up if not scrolled
return CmdResult::NoChange; // Cannot move up if not scrolled
}
if self.line.left() {
self.parser.process(&[27, 91, 68]);
}
CmdResult::None
CmdResult::NoChange
}
Cmd::Move(Direction::Right) => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move up if not scrolled
return CmdResult::NoChange; // Cannot move up if not scrolled
}
if self.line.right() {
self.parser.process(&[27, 91, 67]);
}
CmdResult::None
CmdResult::NoChange
}
Cmd::Move(Direction::Up) => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move up if not scrolled
return CmdResult::NoChange; // Cannot move up if not scrolled
}
self.history_prev();
CmdResult::None
CmdResult::NoChange
}
Cmd::Cancel => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move to the beginning if scrolled
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
}
if !self.line.is_empty() {
@@ -215,7 +222,7 @@ impl MockComponent for TerminalComponent {
}
Cmd::Delete => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move to the beginning if scrolled
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
}
if !self.line.is_empty() {
@@ -231,40 +238,40 @@ impl MockComponent for TerminalComponent {
self.scroll = self.scroll.saturating_sub(8);
self.parser.set_scrollback(self.scroll);
CmdResult::None
CmdResult::NoChange
}
Cmd::Scroll(Direction::Up) => {
self.parser.set_scrollback(self.scroll.saturating_add(8));
let scrollback = self.parser.screen().scrollback();
self.scroll = scrollback;
CmdResult::None
CmdResult::NoChange
}
Cmd::Toggle => {
// insert
self.parser.process(&[27, 91, 50, 126]); // Toggle insert mode
CmdResult::None
CmdResult::NoChange
}
Cmd::GoTo(Position::Begin) => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move to the beginning if scrolled
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
}
for _ in 0..self.line.begin() {
self.parser.process(&[27, 91, 68]); // Move cursor to the left
}
CmdResult::None
CmdResult::NoChange
}
Cmd::GoTo(Position::End) => {
if self.scroll > 0 {
return CmdResult::None; // Cannot move to the beginning if scrolled
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
}
for _ in 0..self.line.end() {
self.parser.process(&[27, 91, 67]); // Move cursor to the right
}
CmdResult::None
CmdResult::NoChange
}
Cmd::Submit => {
self.scroll = 0; // Reset scroll on submit
@@ -281,9 +288,9 @@ impl MockComponent for TerminalComponent {
self.history.push(&line);
}
CmdResult::Submit(State::One(StateValue::String(line)))
CmdResult::Submit(State::Single(StateValue::String(line)))
}
_ => CmdResult::None,
_ => CmdResult::NoChange,
}
}
}
@@ -6,15 +6,16 @@ mod file_list;
mod file_list_with_search;
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{Alignment, Borders, Color, TextSpan};
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
use tuirealm::props::{Borders, Color, HorizontalAlignment, SpanStatic, Title};
use tuirealm::state::{State, StateValue};
use self::file_list::FileList;
use self::file_list_with_search::FileListWithSearch;
use super::{Msg, TransferMsg, UiMsg};
#[derive(MockComponent)]
#[derive(Component)]
pub struct ExplorerFuzzy {
component: FileListWithSearch,
}
@@ -26,13 +27,22 @@ impl ExplorerFuzzy {
.background(bg)
.borders(Borders::default().color(hg))
.foreground(fg)
.highlighted_color(hg)
.title(title, Alignment::Left)
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect()),
.highlight_color(hg)
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
.rows(
files
.iter()
.map(|x| {
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
(*x).to_string(),
))]
})
.collect(),
),
}
}
fn on_search(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
fn on_search(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
@@ -79,8 +89,8 @@ impl ExplorerFuzzy {
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
..
}) => match self.perform(Cmd::Type(ch)) {
CmdResult::Changed(State::One(StateValue::String(search))) => {
}) => match self.perform(Cmd::Type(*ch)) {
CmdResult::Changed(State::Single(StateValue::String(search))) => {
Some(Msg::Ui(UiMsg::FuzzySearch(search)))
}
_ => Some(Msg::None),
@@ -92,7 +102,7 @@ impl ExplorerFuzzy {
}
}
fn on_file_list(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
fn on_file_list(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Down, ..
@@ -145,7 +155,7 @@ impl ExplorerFuzzy {
code: Key::Char('m'),
modifiers: KeyModifiers::NONE,
}) => {
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
self.perform(Cmd::Toggle)
else {
return Some(Msg::None);
@@ -217,8 +227,8 @@ impl ExplorerFuzzy {
}
}
impl Component<Msg, NoUserEvent> for ExplorerFuzzy {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ExplorerFuzzy {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match self.component.focus() {
file_list_with_search::Focus::List => self.on_file_list(ev),
file_list_with_search::Focus::Search => self.on_search(ev),
@@ -226,7 +236,7 @@ impl Component<Msg, NoUserEvent> for ExplorerFuzzy {
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct ExplorerFind {
component: FileList,
}
@@ -238,15 +248,24 @@ impl ExplorerFind {
.background(bg)
.borders(Borders::default().color(hg))
.foreground(fg)
.highlighted_color(hg)
.title(title, Alignment::Left)
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect()),
.highlight_color(hg)
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
.rows(
files
.iter()
.map(|x| {
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
(*x).to_string(),
))]
})
.collect(),
),
}
}
}
impl Component<Msg, NoUserEvent> for ExplorerFind {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ExplorerFind {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Down, ..
@@ -299,7 +318,7 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
code: Key::Char('m'),
modifiers: KeyModifiers::NONE,
}) => {
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
self.perform(Cmd::Toggle)
else {
return Some(Msg::None);
@@ -367,7 +386,7 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct ExplorerLocal {
component: FileList,
}
@@ -379,16 +398,25 @@ impl ExplorerLocal {
.background(bg)
.borders(Borders::default().color(hg))
.foreground(fg)
.highlighted_color(hg)
.title(title, Alignment::Left)
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect())
.highlight_color(hg)
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
.rows(
files
.iter()
.map(|x| {
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
(*x).to_string(),
))]
})
.collect(),
)
.dot_dot(true),
}
}
}
impl Component<Msg, NoUserEvent> for ExplorerLocal {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ExplorerLocal {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Down, ..
@@ -441,7 +469,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
code: Key::Char('m'),
modifiers: KeyModifiers::NONE,
}) => {
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
self.perform(Cmd::Toggle)
else {
return Some(Msg::None);
@@ -464,7 +492,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
if matches!(self.component.state(), State::One(StateValue::String(_))) {
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
Some(Msg::Transfer(TransferMsg::GoToParentDirectory))
} else {
Some(Msg::Transfer(TransferMsg::EnterDirectory))
@@ -474,7 +502,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
code: Key::Char(' '),
..
}) => {
if matches!(self.component.state(), State::One(StateValue::String(_))) {
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
Some(Msg::None)
} else {
Some(Msg::Transfer(TransferMsg::TransferFile))
@@ -585,7 +613,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
}
}
#[derive(MockComponent)]
#[derive(Component)]
pub struct ExplorerRemote {
component: FileList,
}
@@ -597,16 +625,25 @@ impl ExplorerRemote {
.background(bg)
.borders(Borders::default().color(hg))
.foreground(fg)
.highlighted_color(hg)
.title(title, Alignment::Left)
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect())
.highlight_color(hg)
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
.rows(
files
.iter()
.map(|x| {
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
(*x).to_string(),
))]
})
.collect(),
)
.dot_dot(true),
}
}
}
impl Component<Msg, NoUserEvent> for ExplorerRemote {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
impl AppComponent<Msg, NoUserEvent> for ExplorerRemote {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Down, ..
@@ -659,7 +696,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
code: Key::Char('m'),
modifiers: KeyModifiers::NONE,
}) => {
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
self.perform(Cmd::Toggle)
else {
return Some(Msg::None);
@@ -682,7 +719,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => {
if matches!(self.component.state(), State::One(StateValue::String(_))) {
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
Some(Msg::Transfer(TransferMsg::GoToParentDirectory))
} else {
Some(Msg::Transfer(TransferMsg::EnterDirectory))
@@ -692,7 +729,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
code: Key::Char(' '),
..
}) => {
if matches!(self.component.state(), State::One(StateValue::String(_))) {
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
Some(Msg::None)
} else {
Some(Msg::Transfer(TransferMsg::TransferFile))
@@ -3,12 +3,14 @@
//! `FileList` component renders a file list tab
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
use tuirealm::component::Component;
use tuirealm::props::{
Alignment, AttrValue, Attribute, Borders, Color, Style, Table, TextModifiers, TextSpan,
AttrValue, Attribute, Borders, Color, HorizontalAlignment, Props, QueryResult, SpanStatic,
Style, Table, TextModifiers, Title,
};
use tuirealm::ratatui::text::{Line, Span};
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
use tuirealm::{MockComponent, Props, State, StateValue};
use tuirealm::state::{State, StateValue};
pub const FILE_LIST_CMD_SELECT_ALL: &str = "A";
pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D";
@@ -108,16 +110,16 @@ impl FileList {
self
}
pub fn title<S: AsRef<str>>(mut self, t: S, a: Alignment) -> Self {
self.attr(
Attribute::Title,
AttrValue::Title((t.as_ref().to_string(), a)),
);
pub fn title(mut self, t: Title) -> Self {
self.attr(Attribute::Title, AttrValue::Title(t));
self
}
pub fn highlighted_color(mut self, c: Color) -> Self {
self.attr(Attribute::HighlightedColor, AttrValue::Color(c));
pub fn highlight_color(mut self, c: Color) -> Self {
self.attr(
Attribute::HighlightStyle,
AttrValue::Style(Style::default().fg(c).add_modifier(TextModifiers::REVERSED)),
);
self
}
@@ -136,40 +138,57 @@ impl FileList {
fn has_dot_dot(&self) -> bool {
self.props
.get(Attribute::Custom(PROP_DOT_DOT))
.map(AttrValue::unwrap_flag)
.and_then(AttrValue::as_flag)
.unwrap_or(false)
}
}
impl MockComponent for FileList {
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
impl Component for FileList {
fn view(
&mut self,
frame: &mut tuirealm::ratatui::Frame,
area: tuirealm::ratatui::layout::Rect,
) {
let title = self
.props
.get_or(
Attribute::Title,
AttrValue::Title((String::default(), Alignment::Left)),
)
.unwrap_title();
.get(Attribute::Title)
.and_then(AttrValue::as_title)
.cloned()
.unwrap_or_else(|| Title::from(String::default()).alignment(HorizontalAlignment::Left));
let borders = self
.props
.get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
.unwrap_borders();
.get(Attribute::Borders)
.and_then(AttrValue::as_borders)
.unwrap_or_default();
let focus = self
.props
.get_or(Attribute::Focus, AttrValue::Flag(false))
.unwrap_flag();
.get(Attribute::Focus)
.and_then(AttrValue::as_flag)
.unwrap_or(false);
let div = tui_realm_stdlib::utils::get_block(borders, Some(&title), focus, None);
// Make list entries
let init_table_iter = if self.has_dot_dot() {
vec![vec![TextSpan::from("..")]]
vec![vec![tuirealm::props::LineStatic::from(SpanStatic::from(
"..",
))]]
} else {
vec![]
};
let foreground = self
.props
.get(Attribute::Foreground)
.and_then(AttrValue::as_color)
.unwrap_or(Color::Reset);
let background = self
.props
.get(Attribute::Background)
.and_then(AttrValue::as_color)
.unwrap_or(Color::Reset);
let list_items: Vec<ListItem> = match self
.props
.get(Attribute::Content)
.map(AttrValue::unwrap_table)
.and_then(AttrValue::as_table)
{
Some(table) => init_table_iter
.iter()
@@ -177,39 +196,44 @@ impl MockComponent for FileList {
.map(|row| {
let columns: Vec<Span> = row
.iter()
.map(|col| {
let (fg, bg, modifiers) =
tui_realm_stdlib::utils::use_or_default_styles(&self.props, col);
Span::styled(
col.content.clone(),
Style::default().add_modifier(modifiers).fg(fg).bg(bg),
)
.flat_map(|line| line.spans.iter())
.map(|span| {
let mut style = span.style;
if style.fg.is_none() {
style.fg = Some(foreground);
}
if style.bg.is_none() {
style.bg = Some(background);
}
Span::styled(span.content.clone(), style)
})
.collect();
ListItem::new(Line::from(columns))
})
.collect(), // Make List item from TextSpan
.collect(),
_ => Vec::new(),
};
let highlighted_color = self
let highlight_style = self
.props
.get(Attribute::HighlightedColor)
.map(AttrValue::unwrap_color);
let modifiers = match focus {
true => TextModifiers::REVERSED,
false => TextModifiers::empty(),
};
.get(Attribute::HighlightStyle)
.and_then(AttrValue::as_style);
// Make list
let mut list = TuiList::new(list_items)
.block(div)
.direction(ListDirection::TopToBottom);
if let Some(highlighted_color) = highlighted_color {
list = list.highlight_style(
Style::default()
.fg(highlighted_color)
.add_modifier(modifiers),
);
if let Some(style) = highlight_style {
let applied = if focus {
style
} else {
// On blur keep the highlight color on the foreground but drop the
// REVERSED modifier so the selected row just stands out via fg only.
let mut s = Style::default();
if let Some(fg) = style.fg {
s = s.fg(fg);
}
s
};
list = list.highlight_style(applied);
}
let mut state: ListState = ListState::default();
state.select(Some(self.states.list_index));
@@ -219,31 +243,27 @@ impl MockComponent for FileList {
fn attr(&mut self, attr: Attribute, value: AttrValue) {
self.props.set(attr, value);
if matches!(attr, Attribute::Content) {
self.states.init_list_states(
match self
.props
.get(Attribute::Content)
.map(AttrValue::unwrap_table)
{
Some(line) => line.len(),
_ => 0,
},
self.has_dot_dot(),
);
let len = self
.props
.get(Attribute::Content)
.and_then(AttrValue::as_table)
.map(std::vec::Vec::len)
.unwrap_or(0);
self.states.init_list_states(len, self.has_dot_dot());
self.states.fix_list_index();
}
}
fn query(&self, attr: Attribute) -> Option<AttrValue> {
self.props.get(attr)
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
self.props.get_for_query(attr)
}
fn state(&self) -> State {
if self.has_dot_dot() && self.states.list_index == 0 {
return State::One(StateValue::String("..".to_string()));
return State::Single(StateValue::String("..".to_string()));
}
State::One(StateValue::Usize(if self.has_dot_dot() {
State::Single(StateValue::Usize(if self.has_dot_dot() {
self.states.list_index.saturating_sub(1)
} else {
self.states.list_index
@@ -258,7 +278,7 @@ impl MockComponent for FileList {
if prev != self.states.list_index {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Move(Direction::Up) => {
@@ -267,7 +287,7 @@ impl MockComponent for FileList {
if prev != self.states.list_index {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Scroll(Direction::Down) => {
@@ -276,7 +296,7 @@ impl MockComponent for FileList {
if prev != self.states.list_index {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Scroll(Direction::Up) => {
@@ -285,7 +305,7 @@ impl MockComponent for FileList {
if prev != self.states.list_index {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::GoTo(Position::Begin) => {
@@ -294,7 +314,7 @@ impl MockComponent for FileList {
if prev != self.states.list_index {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::GoTo(Position::End) => {
@@ -303,12 +323,12 @@ impl MockComponent for FileList {
if prev != self.states.list_index {
CmdResult::Changed(self.state())
} else {
CmdResult::None
CmdResult::NoChange
}
}
Cmd::Toggle => {
if self.states.list_index == 0 && self.has_dot_dot() {
return CmdResult::None;
return CmdResult::NoChange;
}
let index = self.states.real_index();
@@ -317,9 +337,9 @@ impl MockComponent for FileList {
.list_index
.saturating_add(1)
.min(self.states.list_len.saturating_sub(1));
CmdResult::Changed(State::One(StateValue::Usize(index)))
CmdResult::Changed(State::Single(StateValue::Usize(index)))
}
_ => CmdResult::None,
_ => CmdResult::NoChange,
}
}
}
@@ -1,8 +1,12 @@
use tui_realm_stdlib::Input;
use tui_realm_stdlib::components::Input;
use tuirealm::command::{Cmd, CmdResult};
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Table};
use tuirealm::component::Component;
use tuirealm::props::{
AttrValue, Attribute, Borders, Color, HorizontalAlignment, QueryResult, Style, Table,
TextModifiers, Title,
};
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
use tuirealm::{MockComponent, State};
use tuirealm::state::State;
use super::file_list::FileList;
@@ -62,21 +66,21 @@ impl FileListWithSearch {
self
}
pub fn title<S: AsRef<str>>(mut self, t: S, a: Alignment) -> Self {
self.file_list.attr(
Attribute::Title,
AttrValue::Title((t.as_ref().to_string(), a)),
);
pub fn title(mut self, t: Title) -> Self {
let align = t.content.alignment.unwrap_or(HorizontalAlignment::Left);
self.file_list.attr(Attribute::Title, AttrValue::Title(t));
self.search.attr(
Attribute::Title,
AttrValue::Title(("Fuzzy search".to_string(), a)),
AttrValue::Title(Title::from("Fuzzy search".to_string()).alignment(align)),
);
self
}
pub fn highlighted_color(mut self, c: Color) -> Self {
self.file_list
.attr(Attribute::HighlightedColor, AttrValue::Color(c));
pub fn highlight_color(mut self, c: Color) -> Self {
self.file_list.attr(
Attribute::HighlightStyle,
AttrValue::Style(Style::default().fg(c).add_modifier(TextModifiers::REVERSED)),
);
self
}
@@ -87,8 +91,12 @@ impl FileListWithSearch {
}
}
impl MockComponent for FileListWithSearch {
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
impl Component for FileListWithSearch {
fn view(
&mut self,
frame: &mut tuirealm::ratatui::Frame,
area: tuirealm::ratatui::layout::Rect,
) {
// split the area in two
let chunks = Layout::default()
.direction(Direction::Vertical)
@@ -107,7 +115,7 @@ impl MockComponent for FileListWithSearch {
self.file_list.view(frame, chunks[1]);
}
fn query(&self, attr: Attribute) -> Option<AttrValue> {
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
self.file_list.query(attr)
}
@@ -151,7 +159,7 @@ impl MockComponent for FileListWithSearch {
AttrValue::Flag(self.states.focus == Focus::List),
);
CmdResult::None
CmdResult::NoChange
}
cmd if self.states.focus == Focus::Search => self.search.perform(cmd),
cmd => self.file_list.perform(cmd),
+5 -2
View File
@@ -3,14 +3,17 @@ mod host;
mod log;
mod notify;
use tuirealm::{PollStrategy, Update};
use tuirealm::application::PollStrategy;
use super::FileTransferActivity;
impl FileTransferActivity {
/// Call `Application::tick()` and process messages in `Update`
pub(super) fn tick(&mut self) {
match self.app.tick(PollStrategy::UpTo(1)) {
match self
.app
.tick(PollStrategy::UpTo(1, std::time::Duration::from_millis(10)))
{
Ok(messages) => {
if !messages.is_empty() {
self.redraw = true;
+25 -20
View File
@@ -1,6 +1,8 @@
use tuirealm::props::{
Alignment, AttrValue, Attribute, PropPayload, PropValue, TextModifiers, TextSpan,
AttrValue, Attribute, HorizontalAlignment, LineStatic, PropPayload, PropValue, SpanStatic,
TextModifiers, Title,
};
use tuirealm::terminal::TerminalAdapter;
use super::super::browser::FileExplorerTab;
use super::super::{FileTransferActivity, Id, ui_result};
@@ -33,17 +35,18 @@ impl FileTransferActivity {
hostname.len() + 3
) // 3 because of '/…/'
);
let files: Vec<Vec<TextSpan>> = self
let files: Vec<Vec<LineStatic>> = self
.host_bridge()
.iter_files()
.map(|x| {
let mut span = TextSpan::from(self.host_bridge().fmt_file(x));
let mut span = SpanStatic::from(self.host_bridge().fmt_file(x));
if self.host_bridge().enqueued().contains_key(x.path()) {
span.modifiers |=
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
span.style = span.style.add_modifier(
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC,
);
}
vec![span]
vec![LineStatic::from(span)]
})
.collect();
// Update content and title
@@ -55,7 +58,7 @@ impl FileTransferActivity {
ui_result(self.app.attr(
&Id::ExplorerHostBridge,
Attribute::Title,
AttrValue::Title((hostname, Alignment::Left)),
AttrValue::Title(Title::from(hostname).alignment(HorizontalAlignment::Left)),
));
}
@@ -147,17 +150,18 @@ impl FileTransferActivity {
hostname.len() + 3 // 3 because of '/…/'
)
);
let files: Vec<Vec<TextSpan>> = self
let files: Vec<Vec<LineStatic>> = self
.remote()
.iter_files()
.map(|x| {
let mut span = TextSpan::from(self.remote().fmt_file(x));
let mut span = SpanStatic::from(self.remote().fmt_file(x));
if self.remote().enqueued().contains_key(x.path()) {
span.modifiers |=
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
span.style = span.style.add_modifier(
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC,
);
}
vec![span]
vec![LineStatic::from(span)]
})
.collect();
// Update content and title
@@ -169,7 +173,7 @@ impl FileTransferActivity {
ui_result(self.app.attr(
&Id::ExplorerRemote,
Attribute::Title,
AttrValue::Title((hostname, Alignment::Left)),
AttrValue::Title(Title::from(hostname).alignment(HorizontalAlignment::Left)),
));
}
@@ -185,7 +189,7 @@ impl FileTransferActivity {
ui_result(self.app.attr(
&Id::TransferProgressBar,
Attribute::Value,
AttrValue::Payload(PropPayload::One(PropValue::F64(
AttrValue::Payload(PropPayload::Single(PropValue::F64(
self.transfer.progress.calc_progress(),
))),
));
@@ -201,7 +205,7 @@ impl FileTransferActivity {
ui_result(self.app.attr(
&Id::TransferProgressBar,
Attribute::Title,
AttrValue::Title((title, Alignment::Center)),
AttrValue::Title(Title::from(title).alignment(HorizontalAlignment::Center)),
));
}
@@ -231,17 +235,18 @@ impl FileTransferActivity {
}
pub(in crate::ui::activities::filetransfer) fn update_find_list(&mut self) {
let files: Vec<Vec<TextSpan>> = self
let files: Vec<Vec<LineStatic>> = self
.found()
.unwrap()
.iter_files()
.map(|x| {
let mut span = TextSpan::from(self.found().unwrap().fmt_file(x));
let mut span = SpanStatic::from(self.found().unwrap().fmt_file(x));
if self.found().unwrap().enqueued().contains_key(x.path()) {
span.modifiers |=
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
span.style = span.style.add_modifier(
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC,
);
}
vec![span]
vec![LineStatic::from(span)]
})
.collect();
ui_result(self.app.attr(
+12 -19
View File
@@ -1,4 +1,5 @@
use tuirealm::props::{AttrValue, Attribute, Color, TableBuilder, TextSpan};
use tuirealm::props::{AttrValue, Attribute, Color, SpanStatic, TableBuilder};
use tuirealm::ratatui::style::Stylize;
use super::super::{FileTransferActivity, Id, LogLevel, LogRecord, ui_result};
@@ -52,28 +53,20 @@ impl FileTransferActivity {
LogLevel::Warn => Color::Yellow,
LogLevel::Info => Color::Green,
};
let level_label = match record.level {
LogLevel::Error => "ERROR",
LogLevel::Warn => "WARN",
LogLevel::Info => "INFO",
};
table
.add_col(TextSpan::from(format!(
.add_col(SpanStatic::from(format!(
"{}",
record.time.format("%Y-%m-%dT%H:%M:%S%Z")
)))
.add_col(TextSpan::from(" ["))
.add_col(
TextSpan::new(
format!(
"{:5}",
match record.level {
LogLevel::Error => "ERROR",
LogLevel::Warn => "WARN",
LogLevel::Info => "INFO",
}
)
.as_str(),
)
.fg(fg),
)
.add_col(TextSpan::from("]: "))
.add_col(TextSpan::from(record.msg.as_str()));
.add_col(SpanStatic::from(" ["))
.add_col(SpanStatic::raw(format!("{level_label:5}")).fg(fg))
.add_col(SpanStatic::from("]: "))
.add_col(SpanStatic::from(record.msg.clone()));
}
ui_result(self.app.attr(
&Id::Log,
+4 -4
View File
@@ -5,7 +5,7 @@
// locals
// externals
use remotefs::fs::File;
use tuirealm::{State, StateValue, Update};
use tuirealm::state::{State, StateValue};
use super::actions::SelectedFile;
use super::actions::walkdir::WalkdirError;
@@ -15,8 +15,8 @@ use super::{
ui_result,
};
impl Update<Msg> for FileTransferActivity {
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
impl FileTransferActivity {
pub(super) fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
match msg.unwrap_or(Msg::None) {
Msg::None => None,
Msg::PendingAction(_) => {
@@ -78,7 +78,7 @@ impl FileTransferActivity {
self.action_find_delete();
// Remove deleted entries from the find-result list
match self.app.state(&Id::ExplorerFind) {
Ok(State::One(StateValue::Usize(idx))) => {
Ok(State::Single(StateValue::Usize(idx))) => {
self.found_mut().unwrap().del_entry(idx);
}
Ok(State::Vec(values)) => {
+25 -20
View File
@@ -3,9 +3,11 @@
//! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::Attribute;
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
use tuirealm::ratatui::widgets::Clear;
use tuirealm::{Attribute, Sub, SubClause, SubEventClause};
use tuirealm::subscription::{EventClause, Sub, SubClause};
use tuirealm::terminal::TerminalAdapter;
use unicode_width::UnicodeWidthStr;
use crate::ui::activities::filetransfer::browser::FoundExplorerTab;
@@ -215,7 +217,12 @@ impl FileTransferActivity {
let lines = self
.app
.query(&Id::WaitPopup, Attribute::Text)
.map(|x| x.map(|x| x.unwrap_payload().unwrap_vec().len()))
.map(|x| {
x.map(|x| match x.into_attr() {
tuirealm::props::AttrValue::Text(t) => t.lines.len(),
_ => 1,
})
})
.unwrap_or_default()
.unwrap_or(1) as u16;
let popup =
@@ -270,21 +277,19 @@ impl FileTransferActivity {
// Get current text width
let text_width = self
.app
.query(&id, tuirealm::Attribute::Text)
.query(&id, tuirealm::props::Attribute::Text)
.ok()
.flatten()
.map(|x| {
if x.as_payload().is_none() {
return 0;
}
x.unwrap_payload()
.unwrap_vec()
.into_iter()
.map(|x| x.unwrap_text_span().content)
.collect::<Vec<String>>()
.map(|x| match x.into_attr() {
tuirealm::props::AttrValue::Text(t) => t
.lines
.iter()
.flat_map(|line| line.spans.iter())
.map(|span| span.content.as_ref())
.collect::<Vec<&str>>()
.join("")
.width() as u16
.width() as u16,
_ => 0,
})
.unwrap_or(0);
// Calc real width of a row in the popup
@@ -305,41 +310,41 @@ impl FileTransferActivity {
Box::<components::GlobalListener>::default(),
vec![
Sub::new(
SubEventClause::Keyboard(KeyEvent {
EventClause::Keyboard(KeyEvent {
code: Key::Esc,
modifiers: KeyModifiers::NONE,
}),
Self::no_popup_mounted_clause(),
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
EventClause::Keyboard(KeyEvent {
code: Key::Char('h'),
modifiers: KeyModifiers::NONE,
}),
Self::no_popup_mounted_clause(),
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
EventClause::Keyboard(KeyEvent {
code: Key::Function(1),
modifiers: KeyModifiers::NONE,
}),
Self::no_popup_mounted_clause(),
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
EventClause::Keyboard(KeyEvent {
code: Key::Function(10),
modifiers: KeyModifiers::NONE,
}),
Self::no_popup_mounted_clause(),
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
EventClause::Keyboard(KeyEvent {
code: Key::Char('q'),
modifiers: KeyModifiers::NONE,
}),
Self::no_popup_mounted_clause(),
),
Sub::new(SubEventClause::WindowResize, SubClause::Always),
Sub::new(EventClause::WindowResize, SubClause::Always),
],
));
}
@@ -3,8 +3,7 @@
//! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall
use remotefs::fs::{File, UnixPex};
use tuirealm::props::{PropPayload, PropValue, TextSpan};
use tuirealm::{AttrValue, Attribute};
use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, SpanStatic};
use crate::explorer::FileSorting;
use crate::ui::activities::filetransfer::browser::FileExplorerTab;
@@ -93,9 +92,9 @@ impl FileTransferActivity {
let _ = self.app.attr(
&Id::WaitPopup,
Attribute::Text,
AttrValue::Payload(PropPayload::Vec(vec![
PropValue::TextSpan(TextSpan::from(text)),
PropValue::TextSpan(TextSpan::from("Press 'CTRL+C' to abort")),
AttrValue::Text(tuirealm::ratatui::text::Text::from_iter([
SpanStatic::from(text),
SpanStatic::from("Press 'CTRL+C' to abort"),
])),
);