diff --git a/Cargo.lock b/Cargo.lock index 50ca57b..667f088 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,7 @@ dependencies = [ "http 0.2.12", "http 1.4.0", "http-body 1.0.1", - "lru 0.16.3", + "lru", "percent-encoding", "regex-lite", "sha2 0.10.9", @@ -812,12 +812,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "castaway" version = "0.2.4" @@ -912,9 +906,9 @@ checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96" [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -1094,22 +1088,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.11.0", - "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.29.0" @@ -2101,8 +2079,6 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash 0.1.5", ] @@ -2448,7 +2424,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -2737,9 +2713,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -2798,6 +2774,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + [[package]] name = "keyring" version = "3.6.3" @@ -3078,6 +3065,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -3117,15 +3113,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "lru" version = "0.16.3" @@ -3682,12 +3669,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pastey" version = "0.1.1" @@ -4086,7 +4067,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.37", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -4123,7 +4104,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -4216,22 +4197,64 @@ checksum = "f66ee92bc15280519ef199a274fe0cafff4245d31bc39aaa31c011ad56cb1f05" [[package]] name = "ratatui" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.11.0", - "cassowary", "compact_str", - "crossterm 0.28.1", + "hashbrown 0.16.1", + "indoc", + "itertools", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.16.1", "indoc", "instability", "itertools", - "lru 0.12.5", - "paste", + "line-clipping", + "ratatui-core", "strum", + "time", "unicode-segmentation", - "unicode-truncate", "unicode-width 0.2.0", ] @@ -5577,23 +5600,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", "syn", ] @@ -6184,9 +6206,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tui-realm-stdlib" -version = "3.1.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333d8b8da2b3479a68fdf4bd2621f888fa869501d6c5c72bac940d0eeb19b083" +checksum = "16a0e19fc8b6ea3a7a88d51ae8df4d657fb4f9a1cdf79f354217e5136838d5dd" dependencies = [ "textwrap", "tuirealm", @@ -6195,22 +6217,23 @@ dependencies = [ [[package]] name = "tui-term" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72af159125ce32b02ceaced6cffae6394b0e6b6dfd4dc164a6c59a2db9b3c0b0" +checksum = "f740cf128bd2ecffea958989646e79a546c45772dac6e42c0b86451185ec6933" dependencies = [ - "ratatui", + "ratatui-core", + "ratatui-widgets", "vt100", ] [[package]] name = "tuirealm" -version = "3.3.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a96abaf33552e9e2487bf56f537e49fad1d1fbc55a3fd542449d4e30e7c8f3" +checksum = "257b2348be6fc22fad4b6ba6288207016d88417c071fc96268b5865e24662305" dependencies = [ "bitflags 2.11.0", - "crossterm 0.29.0", + "crossterm", "dyn-clone", "lazy-regex", "ratatui", @@ -6220,9 +6243,9 @@ dependencies = [ [[package]] name = "tuirealm_derive" -version = "2.0.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad3c151090da5a036321776209b3d026df637d7f52c0984ff8d45927326ab4f" +checksum = "19eb242ebcb03507baa4a13392bdfc019c54dbb74492fe5e8b75d1b69ea78ecf" dependencies = [ "proc-macro2", "quote", @@ -6279,13 +6302,13 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] @@ -6721,7 +6744,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4bc5993..81268a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,9 +86,9 @@ tempfile = "3" thiserror = "2" tokio = { version = "1", features = ["rt"] } toml = "1" -tui-realm-stdlib = "3" -tui-term = "0.2" -tuirealm = "3" +tui-realm-stdlib = "4" +tui-term = "0.3" +tuirealm = "4" unicode-width = "0.2" whoami = "2" wildmatch = "2" diff --git a/src/config/serialization.rs b/src/config/serialization.rs index cbc7f80..d945e8c 100644 --- a/src/config/serialization.rs +++ b/src/config/serialization.rs @@ -611,10 +611,12 @@ mod tests { toml_file.as_file().sync_all().unwrap(); toml_file.as_file().rewind().unwrap(); assert!(deserialize::(Box::new(toml_file)).is_ok()); + // Malformed theme files must still load successfully; unknown or invalid + // fields fall back to defaults so user themes remain backwards compatible. let toml_file = create_bad_toml_theme(); toml_file.as_file().sync_all().unwrap(); toml_file.as_file().rewind().unwrap(); - assert!(deserialize::(Box::new(toml_file)).is_err()); + assert!(deserialize::(Box::new(toml_file)).is_ok()); } #[test] diff --git a/src/config/themes.rs b/src/config/themes.rs index 32f938f..1803731 100644 --- a/src/config/themes.rs +++ b/src/config/themes.rs @@ -12,142 +12,62 @@ use crate::utils::fmt::fmt_color; use crate::utils::parser::parse_color; /// Theme contains all the colors lookup table for termscp -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct Theme { // -- auth - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_address: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_bookmarks: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_password: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_port: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_protocol: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_recents: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub auth_username: Color, // -- misc - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_error_dialog: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_info_dialog: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_input_dialog: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_keys: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_quit_dialog: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_save_dialog: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub misc_warn_dialog: Color, // -- transfer - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_local_explorer_background: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_local_explorer_foreground: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_local_explorer_highlighted: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_log_background: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_log_window: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color", - alias = "transfer_progress_bar_full", - alias = "transfer_progress_bar_partial" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_progress_bar: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_remote_explorer_background: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_remote_explorer_foreground: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_remote_explorer_highlighted: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_status_hidden: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_status_sorting: Color, - #[serde( - deserialize_with = "deserialize_color", - serialize_with = "serialize_color" - )] + #[serde(serialize_with = "serialize_color")] pub transfer_status_sync_browsing: Color, } @@ -185,16 +105,124 @@ impl Default for Theme { } // -- deserializer +// +// Custom deserialization: every field is optional and falls back to `Theme::default()` +// when missing or when the supplied color string is invalid. This keeps user themes +// backwards compatible even when fields are added, renamed, or contain typos. -fn deserialize_color<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let s: String = Deserialize::deserialize(deserializer)?; - // Parse color - match parse_color(&s) { - None => Err(DeError::custom("Invalid color")), - Some(color) => Ok(color), +#[derive(Deserialize, Default)] +#[serde(default)] +struct ThemeFile { + auth_address: Option, + auth_bookmarks: Option, + auth_password: Option, + auth_port: Option, + auth_protocol: Option, + auth_recents: Option, + auth_username: Option, + misc_error_dialog: Option, + misc_info_dialog: Option, + misc_input_dialog: Option, + misc_keys: Option, + misc_quit_dialog: Option, + misc_save_dialog: Option, + misc_warn_dialog: Option, + transfer_local_explorer_background: Option, + transfer_local_explorer_foreground: Option, + transfer_local_explorer_highlighted: Option, + transfer_log_background: Option, + transfer_log_window: Option, + transfer_progress_bar: Option, + // Legacy aliases for the now-consolidated `transfer_progress_bar` field. + transfer_progress_bar_full: Option, + transfer_progress_bar_partial: Option, + transfer_remote_explorer_background: Option, + transfer_remote_explorer_foreground: Option, + transfer_remote_explorer_highlighted: Option, + transfer_status_hidden: Option, + transfer_status_sorting: Option, + transfer_status_sync_browsing: Option, +} + +impl ThemeFile { + fn into_theme(self) -> Theme { + let defaults = Theme::default(); + fn pick(value: Option, fallback: Color) -> Color { + value.as_deref().and_then(parse_color).unwrap_or(fallback) + } + Theme { + auth_address: pick(self.auth_address, defaults.auth_address), + auth_bookmarks: pick(self.auth_bookmarks, defaults.auth_bookmarks), + auth_password: pick(self.auth_password, defaults.auth_password), + auth_port: pick(self.auth_port, defaults.auth_port), + auth_protocol: pick(self.auth_protocol, defaults.auth_protocol), + auth_recents: pick(self.auth_recents, defaults.auth_recents), + auth_username: pick(self.auth_username, defaults.auth_username), + misc_error_dialog: pick(self.misc_error_dialog, defaults.misc_error_dialog), + misc_info_dialog: pick(self.misc_info_dialog, defaults.misc_info_dialog), + misc_input_dialog: pick(self.misc_input_dialog, defaults.misc_input_dialog), + misc_keys: pick(self.misc_keys, defaults.misc_keys), + misc_quit_dialog: pick(self.misc_quit_dialog, defaults.misc_quit_dialog), + misc_save_dialog: pick(self.misc_save_dialog, defaults.misc_save_dialog), + misc_warn_dialog: pick(self.misc_warn_dialog, defaults.misc_warn_dialog), + transfer_local_explorer_background: pick( + self.transfer_local_explorer_background, + defaults.transfer_local_explorer_background, + ), + transfer_local_explorer_foreground: pick( + self.transfer_local_explorer_foreground, + defaults.transfer_local_explorer_foreground, + ), + transfer_local_explorer_highlighted: pick( + self.transfer_local_explorer_highlighted, + defaults.transfer_local_explorer_highlighted, + ), + transfer_log_background: pick( + self.transfer_log_background, + defaults.transfer_log_background, + ), + transfer_log_window: pick(self.transfer_log_window, defaults.transfer_log_window), + transfer_progress_bar: pick( + self.transfer_progress_bar + .or(self.transfer_progress_bar_full) + .or(self.transfer_progress_bar_partial), + defaults.transfer_progress_bar, + ), + transfer_remote_explorer_background: pick( + self.transfer_remote_explorer_background, + defaults.transfer_remote_explorer_background, + ), + transfer_remote_explorer_foreground: pick( + self.transfer_remote_explorer_foreground, + defaults.transfer_remote_explorer_foreground, + ), + transfer_remote_explorer_highlighted: pick( + self.transfer_remote_explorer_highlighted, + defaults.transfer_remote_explorer_highlighted, + ), + transfer_status_hidden: pick( + self.transfer_status_hidden, + defaults.transfer_status_hidden, + ), + transfer_status_sorting: pick( + self.transfer_status_sorting, + defaults.transfer_status_sorting, + ), + transfer_status_sync_browsing: pick( + self.transfer_status_sync_browsing, + defaults.transfer_status_sync_browsing, + ), + } + } +} + +impl<'de> Deserialize<'de> for Theme { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let file = ThemeFile::deserialize(deserializer)?; + Ok(file.into_theme()) } } @@ -207,14 +235,29 @@ where serializer.serialize_str(s.as_str()) } +// Kept for backwards compatibility with any external callers; no longer used directly +// by serde derive because `Theme` now uses a custom `Deserialize` impl. +#[allow(dead_code)] +fn deserialize_color<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer)?; + match parse_color(&s) { + None => Err(DeError::custom("Invalid color")), + Some(color) => Ok(color), + } +} + #[cfg(test)] -mod test { +mod tests { + use pretty_assertions::assert_eq; use super::*; #[test] - fn test_config_themes_default() { + fn should_get_default_theme() { let theme: Theme = Theme::default(); assert_eq!(theme.auth_address, Color::Yellow); assert_eq!(theme.auth_bookmarks, Color::LightGreen); @@ -243,4 +286,48 @@ mod test { assert_eq!(theme.transfer_status_sorting, Color::LightYellow); assert_eq!(theme.transfer_status_sync_browsing, Color::LightGreen); } + + #[test] + fn should_accept_legacy_progress_bar_fields() { + let toml = r#" + auth_protocol = "Yellow" + transfer_progress_bar_full = "Green" + "#; + let theme: Theme = toml::from_str(toml).expect("theme should load"); + assert_eq!(theme.auth_protocol, Color::Yellow); + assert_eq!(theme.transfer_progress_bar, Color::Green); + } + + #[test] + fn should_ignore_duplicated_legacy_progress_bar_fields() { + let toml = r#" + auth_protocol = "Yellow" + transfer_progress_bar_full = "Green" + transfer_progress_bar_partial = "Red" + "#; + let theme: Theme = toml::from_str(toml).expect("theme should load"); + assert_eq!(theme.auth_protocol, Color::Yellow); + // `_full` wins because `transfer_progress_bar` and `_full` are checked first. + assert_eq!(theme.transfer_progress_bar, Color::Green); + } + + #[test] + fn should_fall_back_to_defaults_on_invalid_values() { + // Invalid color value should make *that* field fall back to default, without + // breaking the rest of the theme. + let toml = r##" + auth_protocol = "not-a-color" + auth_username = "#ca9ee6" + "##; + let theme: Theme = toml::from_str(toml).expect("theme should load"); + assert_eq!(theme.auth_protocol, Theme::default().auth_protocol); + assert_eq!(theme.auth_username, Color::Rgb(202, 158, 230)); + } + + #[test] + fn should_fall_back_to_defaults_on_missing_fields() { + // Empty file should still produce a theme equal to default. + let theme: Theme = toml::from_str("").expect("theme should load"); + assert_eq!(theme, Theme::default()); + } } diff --git a/src/ui/activities/auth.rs b/src/ui/activities/auth.rs index ff6e376..5319abe 100644 --- a/src/ui/activities/auth.rs +++ b/src/ui/activities/auth.rs @@ -13,9 +13,10 @@ mod view; // Includes use std::time::Duration; -use tuirealm::application::PollStrategy; +use tuirealm::application::{Application, PollStrategy}; +use tuirealm::event::NoUserEvent; use tuirealm::listener::EventListenerCfg; -use tuirealm::{Application, NoUserEvent, Update}; +use tuirealm::terminal::TerminalAdapter; use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason}; use crate::config::themes::Theme; @@ -254,9 +255,7 @@ impl AuthActivity { pub fn new(ticks: Duration) -> AuthActivity { AuthActivity { app: Application::init( - EventListenerCfg::default() - .crossterm_input_listener(ticks, CROSSTERM_MAX_POLL) - .poll_timeout(ticks), + EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL), ), context: None, bookmarks_list: Vec::new(), @@ -369,7 +368,10 @@ impl Activity for AuthActivity { return; } // Tick - match self.app.tick(PollStrategy::UpTo(3)) { + match self + .app + .tick(PollStrategy::UpTo(3, std::time::Duration::from_millis(10))) + { Ok(messages) => { for msg in messages.into_iter() { let mut msg = Some(msg); diff --git a/src/ui/activities/auth/components.rs b/src/ui/activities/auth/components.rs index d5d8f85..1b82da9 100644 --- a/src/ui/activities/auth/components.rs +++ b/src/ui/activities/auth/components.rs @@ -28,19 +28,19 @@ pub use popup::{ WindowSizeError, }; pub use text::{HelpFooter, NewVersionDisclaimer, Subtitle, Title}; -use tui_realm_stdlib::Phantom; +use tui_realm_stdlib::components::Phantom; +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent}; -use tuirealm::{Component, MockComponent}; // -- global listener -#[derive(Default, MockComponent)] +#[derive(Default, Component)] pub struct GlobalListener { component: Phantom, } -impl Component for GlobalListener { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for GlobalListener { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Function(10), diff --git a/src/ui/activities/auth/components/bookmarks.rs b/src/ui/activities/auth/components/bookmarks.rs index 31f70cb..a5b5c5a 100644 --- a/src/ui/activities/auth/components/bookmarks.rs +++ b/src/ui/activities/auth/components/bookmarks.rs @@ -2,18 +2,22 @@ //! //! auth activity bookmarks components -use tui_realm_stdlib::{Input, List, Radio}; +use tui_realm_stdlib::components::{Input, List, Radio}; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; -use tuirealm::event::{Key, KeyEvent, KeyModifiers}; -use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, 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::{ + BorderSides, BorderType, Borders, Color, HorizontalAlignment, InputType, SpanStatic, Style, + TextModifiers, Title, +}; +use tuirealm::state::{State, StateValue}; use super::{FormMsg, Msg, UiMsg}; use crate::ui::activities::auth::FormTab; // -- bookmark list -#[derive(MockComponent)] +#[derive(Component)] pub struct BookmarksList { component: List, } @@ -23,23 +27,23 @@ impl BookmarksList { Self { component: List::default() .borders(Borders::default().color(color).modifiers(BorderType::Plain)) - .highlighted_color(color) + .highlight_style(Style::default().fg(color)) .rewind(true) .scroll(true) .step(4) - .title("Bookmarks", Alignment::Left) + .title(Title::from("Bookmarks").alignment(HorizontalAlignment::Left)) .rows( bookmarks .iter() - .map(|x| vec![TextSpan::from(x.as_str())]) - .collect(), + .map(|x| vec![SpanStatic::from(x.clone())]) + .collect::>>(), ), } } } -impl Component for BookmarksList { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for BookmarksList { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -77,7 +81,7 @@ impl Component for BookmarksList { Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => match self.state() { - State::One(StateValue::Usize(choice)) => { + State::Single(StateValue::Usize(choice)) => { Some(Msg::Form(FormMsg::LoadBookmark(choice))) } _ => Some(Msg::None), @@ -98,7 +102,7 @@ impl Component for BookmarksList { // -- recents list -#[derive(MockComponent)] +#[derive(Component)] pub struct RecentsList { component: List, } @@ -108,23 +112,23 @@ impl RecentsList { Self { component: List::default() .borders(Borders::default().color(color).modifiers(BorderType::Plain)) - .highlighted_color(color) + .highlight_style(Style::default().fg(color)) .rewind(true) .scroll(true) .step(4) - .title("Recent connections", Alignment::Left) + .title(Title::from("Recent connections").alignment(HorizontalAlignment::Left)) .rows( bookmarks .iter() - .map(|x| vec![TextSpan::from(x.as_str())]) - .collect(), + .map(|x| vec![SpanStatic::from(x.clone())]) + .collect::>>(), ), } } } -impl Component for RecentsList { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for RecentsList { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -162,7 +166,7 @@ impl Component for RecentsList { Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => match self.state() { - State::One(StateValue::Usize(choice)) => { + State::Single(StateValue::Usize(choice)) => { Some(Msg::Form(FormMsg::LoadRecent(choice))) } _ => Some(Msg::None), @@ -183,7 +187,7 @@ impl Component for RecentsList { // -- delete bookmark -#[derive(MockComponent)] +#[derive(Component)] pub struct DeleteBookmarkPopup { component: Radio, } @@ -192,6 +196,11 @@ impl DeleteBookmarkPopup { 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) @@ -200,14 +209,15 @@ impl DeleteBookmarkPopup { .choices(["Yes", "No"]) .value(1) .rewind(true) - .foreground(color) - .title("Delete selected bookmark?", Alignment::Center), + .title( + Title::from("Delete selected bookmark?").alignment(HorizontalAlignment::Center), + ), } } } -impl Component for DeleteBookmarkPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for DeleteBookmarkPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseDeleteBookmark)) @@ -237,7 +247,7 @@ impl Component for DeleteBookmarkPopup { }) => { if matches!( self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) + CmdResult::Submit(State::Single(StateValue::Usize(0))) ) { Some(Msg::Form(FormMsg::DeleteBookmark)) } else { @@ -251,7 +261,7 @@ impl Component for DeleteBookmarkPopup { // -- delete recent -#[derive(MockComponent)] +#[derive(Component)] pub struct DeleteRecentPopup { component: Radio, } @@ -260,6 +270,11 @@ impl DeleteRecentPopup { 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) @@ -268,14 +283,16 @@ impl DeleteRecentPopup { .choices(["Yes", "No"]) .value(1) .rewind(true) - .foreground(color) - .title("Delete selected recent host?", Alignment::Center), + .title( + Title::from("Delete selected recent host?") + .alignment(HorizontalAlignment::Center), + ), } } } -impl Component for DeleteRecentPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for DeleteRecentPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseDeleteRecent)) @@ -305,7 +322,7 @@ impl Component for DeleteRecentPopup { }) => { if matches!( self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) + CmdResult::Submit(State::Single(StateValue::Usize(0))) ) { Some(Msg::Form(FormMsg::DeleteRecent)) } else { @@ -321,7 +338,7 @@ impl Component for DeleteRecentPopup { // -- save password -#[derive(MockComponent)] +#[derive(Component)] pub struct BookmarkSavePassword { component: Radio, form_tab: FormTab, @@ -331,6 +348,11 @@ impl BookmarkSavePassword { pub fn new(form_tab: FormTab, color: Color) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(color) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::Reset) @@ -340,15 +362,14 @@ impl BookmarkSavePassword { .choices(["Yes", "No"]) .value(0) .rewind(true) - .foreground(color) - .title("Save secrets?", Alignment::Center), + .title(Title::from("Save secrets?").alignment(HorizontalAlignment::Center)), form_tab, } } } -impl Component for BookmarkSavePassword { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for BookmarkSavePassword { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseSaveBookmark)) @@ -378,7 +399,7 @@ impl Component for BookmarkSavePassword { // -- new bookmark name -#[derive(MockComponent)] +#[derive(Component)] pub struct BookmarkName { component: Input, form_tab: FormTab, @@ -395,15 +416,15 @@ impl BookmarkName { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Bookmark name", Alignment::Left) + .title(Title::from("Bookmark name").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text), form_tab, } } } -impl Component for BookmarkName { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for BookmarkName { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseSaveBookmark)) @@ -447,7 +468,7 @@ impl Component for BookmarkName { code: Key::Char(ch), .. }) => { - self.perform(Cmd::Type(ch)); + self.perform(Cmd::Type(*ch)); Some(Msg::None) } Event::Keyboard(KeyEvent { diff --git a/src/ui/activities/auth/components/form.rs b/src/ui/activities/auth/components/form.rs index 742f12d..5979b51 100644 --- a/src/ui/activities/auth/components/form.rs +++ b/src/ui/activities/auth/components/form.rs @@ -2,11 +2,13 @@ //! //! auth activity components for file transfer params form -use tui_realm_stdlib::{Input, Radio}; -use tuirealm::Component; +use tui_realm_stdlib::components::{Input, Radio}; use tuirealm::command::{Cmd, Direction, Position}; +use tuirealm::component::AppComponent; use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent}; -use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style}; +use tuirealm::props::{ + BorderType, Borders, Color, HorizontalAlignment, InputType, Style, TextModifiers, Title, +}; use super::{FileTransferProtocol, FormMsg, Msg, UiMsg}; use crate::ui::activities::auth::{ @@ -51,8 +53,8 @@ pub use smb::InputSmbWorkgroup; pub use webdav::InputWebDAVUri; fn handle_input_ev( - component: &mut dyn Component, - ev: Event, + component: &mut dyn AppComponent, + ev: &Event, on_key_down: Msg, on_key_up: Msg, form_tab: FormTab, @@ -104,7 +106,7 @@ fn handle_input_ev( code: Key::Char(ch), .. }) => { - component.perform(Cmd::Type(ch)); + component.perform(Cmd::Type(*ch)); Some(Msg::None) } Event::Keyboard(KeyEvent { diff --git a/src/ui/activities/auth/components/form/generic.rs b/src/ui/activities/auth/components/form/generic.rs index 994280a..930490d 100644 --- a/src/ui/activities/auth/components/form/generic.rs +++ b/src/ui/activities/auth/components/form/generic.rs @@ -1,9 +1,9 @@ +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::NoUserEvent; -use tuirealm::{Component, MockComponent}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct InputAddress { component: Input, form_tab: FormTab, @@ -19,8 +19,11 @@ impl InputAddress { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("127.0.0.1", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Remote host", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "127.0.0.1", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Remote host").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(host), form_tab, @@ -28,8 +31,8 @@ impl InputAddress { } } -impl Component for InputAddress { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputAddress { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::AddressBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::AddressBlurDown)), @@ -44,7 +47,7 @@ impl Component for InputAddress { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputPort { component: Input, form_tab: FormTab, @@ -60,18 +63,21 @@ impl InputPort { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("22", Style::default().fg(Color::Rgb(128, 128, 128))) + .placeholder(tuirealm::props::SpanStatic::styled( + "22", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) .input_type(InputType::UnsignedInteger) .input_len(5) - .title("Port number", Alignment::Left) + .title(Title::from("Port number").alignment(HorizontalAlignment::Left)) .value(port.to_string()), form_tab, } } } -impl Component for InputPort { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputPort { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::PortBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::PortBlurDown)), @@ -86,7 +92,7 @@ impl Component for InputPort { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputUsername { component: Input, form_tab: FormTab, @@ -102,8 +108,11 @@ impl InputUsername { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("root", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Username", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "root", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Username").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(username), form_tab, @@ -111,8 +120,8 @@ impl InputUsername { } } -impl Component for InputUsername { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputUsername { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::UsernameBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::UsernameBlurDown)), @@ -127,7 +136,7 @@ impl Component for InputUsername { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputPassword { component: Input, form_tab: FormTab, @@ -143,7 +152,7 @@ impl InputPassword { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Password", Alignment::Left) + .title(Title::from("Password").alignment(HorizontalAlignment::Left)) .input_type(InputType::Password('*')) .value(password), form_tab, @@ -151,8 +160,8 @@ impl InputPassword { } } -impl Component for InputPassword { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputPassword { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::PasswordBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::PasswordBlurDown)), diff --git a/src/ui/activities/auth/components/form/kube.rs b/src/ui/activities/auth/components/form/kube.rs index b7683e1..4bfb4dd 100644 --- a/src/ui/activities/auth/components/form/kube.rs +++ b/src/ui/activities/auth/components/form/kube.rs @@ -1,9 +1,9 @@ +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::NoUserEvent; -use tuirealm::{Component, MockComponent}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct InputKubeNamespace { component: Input, form_tab: FormTab, @@ -19,8 +19,11 @@ impl InputKubeNamespace { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("namespace", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Pod namespace (optional)", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "namespace", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Pod namespace (optional)").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(bucket), form_tab, @@ -28,8 +31,8 @@ impl InputKubeNamespace { } } -impl Component for InputKubeNamespace { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputKubeNamespace { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeNamespaceBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::KubeNamespaceBlurDown)), @@ -44,7 +47,7 @@ impl Component for InputKubeNamespace { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputKubeClusterUrl { component: Input, form_tab: FormTab, @@ -60,11 +63,13 @@ impl InputKubeClusterUrl { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "cluster url", Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("Kube cluster url (optional)").alignment(HorizontalAlignment::Left), ) - .title("Kube cluster url (optional)", Alignment::Left) .input_type(InputType::Text) .value(bucket), form_tab, @@ -72,8 +77,8 @@ impl InputKubeClusterUrl { } } -impl Component for InputKubeClusterUrl { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputKubeClusterUrl { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeClusterUrlBlurDown)), FormTab::HostBridge => { @@ -90,7 +95,7 @@ impl Component for InputKubeClusterUrl { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputKubeUsername { component: Input, form_tab: FormTab, @@ -106,8 +111,11 @@ impl InputKubeUsername { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("username", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Kube username (optional)", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "username", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Kube username (optional)").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(bucket), form_tab, @@ -115,8 +123,8 @@ impl InputKubeUsername { } } -impl Component for InputKubeUsername { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputKubeUsername { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeUsernameBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::KubeUsernameBlurDown)), @@ -131,7 +139,7 @@ impl Component for InputKubeUsername { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputKubeClientCert { component: Input, form_tab: FormTab, @@ -147,11 +155,14 @@ impl InputKubeClientCert { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "/home/user/.kube/client.crt", Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("Kube client cert path (optional)") + .alignment(HorizontalAlignment::Left), ) - .title("Kube client cert path (optional)", Alignment::Left) .input_type(InputType::Text) .value(bucket), form_tab, @@ -159,8 +170,8 @@ impl InputKubeClientCert { } } -impl Component for InputKubeClientCert { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputKubeClientCert { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeClientCertBlurDown)), FormTab::HostBridge => { @@ -177,7 +188,7 @@ impl Component for InputKubeClientCert { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputKubeClientKey { component: Input, form_tab: FormTab, @@ -193,11 +204,14 @@ impl InputKubeClientKey { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "/home/user/.kube/client.key", Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("Kube client key path (optional)") + .alignment(HorizontalAlignment::Left), ) - .title("Kube client key path (optional)", Alignment::Left) .input_type(InputType::Text) .value(bucket), form_tab, @@ -205,8 +219,8 @@ impl InputKubeClientKey { } } -impl Component for InputKubeClientKey { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputKubeClientKey { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeClientKeyBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::KubeClientKeyBlurDown)), diff --git a/src/ui/activities/auth/components/form/paths.rs b/src/ui/activities/auth/components/form/paths.rs index d6c9221..735dc4a 100644 --- a/src/ui/activities/auth/components/form/paths.rs +++ b/src/ui/activities/auth/components/form/paths.rs @@ -1,9 +1,9 @@ +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::NoUserEvent; -use tuirealm::{Component, MockComponent}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct InputRemoteDirectory { component: Input, form_tab: FormTab, @@ -19,8 +19,14 @@ impl InputRemoteDirectory { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("/home/foo", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Default remote working directory", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "/home/foo", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("Default remote working directory") + .alignment(HorizontalAlignment::Left), + ) .input_type(InputType::Text) .value(remote_dir), form_tab, @@ -28,8 +34,8 @@ impl InputRemoteDirectory { } } -impl Component for InputRemoteDirectory { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputRemoteDirectory { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::RemoteDirectoryBlurDown)), FormTab::HostBridge => { @@ -46,7 +52,7 @@ impl Component for InputRemoteDirectory { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputLocalDirectory { component: Input, form_tab: FormTab, @@ -62,8 +68,14 @@ impl InputLocalDirectory { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("/home/foo", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Default local working directory", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "/home/foo", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("Default local working directory") + .alignment(HorizontalAlignment::Left), + ) .input_type(InputType::Text) .value(local_dir), form_tab, @@ -71,8 +83,8 @@ impl InputLocalDirectory { } } -impl Component for InputLocalDirectory { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputLocalDirectory { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::LocalDirectoryBlurDown)), FormTab::HostBridge => { diff --git a/src/ui/activities/auth/components/form/protocol.rs b/src/ui/activities/auth/components/form/protocol.rs index 8bea95e..af9d437 100644 --- a/src/ui/activities/auth/components/form/protocol.rs +++ b/src/ui/activities/auth/components/form/protocol.rs @@ -1,11 +1,14 @@ use tuirealm::command::{Cmd, CmdResult, Direction}; +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent}; -use tuirealm::props::{Alignment, BorderType, Borders, Color}; -use tuirealm::{Component, MockComponent, State, StateValue}; +use tuirealm::props::{ + BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title, +}; +use tuirealm::state::{State, StateValue}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct RemoteProtocolRadio { component: Radio, } @@ -14,6 +17,11 @@ impl RemoteProtocolRadio { pub fn new(default_protocol: FileTransferProtocol, color: Color) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(color) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(color) @@ -24,9 +32,8 @@ impl RemoteProtocolRadio { } else { vec!["SFTP", "SCP", "FTP", "FTPS", "S3", "Kube", "WebDAV"].into_iter() }) - .foreground(color) .rewind(true) - .title("Protocol", Alignment::Left) + .title(Title::from("Protocol").alignment(HorizontalAlignment::Left)) .value(Self::protocol_enum_to_opt(default_protocol)), } } @@ -58,8 +65,8 @@ impl RemoteProtocolRadio { } } -impl Component for RemoteProtocolRadio { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for RemoteProtocolRadio { + fn on(&mut self, ev: &Event) -> Option { let result = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -86,7 +93,7 @@ impl Component for RemoteProtocolRadio { }; match result { - CmdResult::Changed(State::One(StateValue::Usize(choice))) => Some(Msg::Form( + CmdResult::Changed(State::Single(StateValue::Usize(choice))) => Some(Msg::Form( FormMsg::RemoteProtocolChanged(Self::protocol_opt_to_enum(choice)), )), _ => Some(Msg::None), @@ -94,7 +101,7 @@ impl Component for RemoteProtocolRadio { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct HostBridgeProtocolRadio { component: Radio, } @@ -103,6 +110,11 @@ impl HostBridgeProtocolRadio { pub fn new(protocol: HostBridgeProtocol, color: Color) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(color) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(color) @@ -134,9 +146,8 @@ impl HostBridgeProtocolRadio { ] .into_iter() }) - .foreground(color) .rewind(true) - .title("Host type", Alignment::Left) + .title(Title::from("Host type").alignment(HorizontalAlignment::Left)) .value(Self::protocol_to_opt(protocol)), } } @@ -195,8 +206,8 @@ impl HostBridgeProtocolRadio { } } -impl Component for HostBridgeProtocolRadio { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for HostBridgeProtocolRadio { + fn on(&mut self, ev: &Event) -> Option { let result = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -223,7 +234,7 @@ impl Component for HostBridgeProtocolRadio { }; match result { - CmdResult::Changed(State::One(StateValue::Usize(choice))) => Some(Msg::Form( + CmdResult::Changed(State::Single(StateValue::Usize(choice))) => Some(Msg::Form( FormMsg::HostBridgeProtocolChanged(Self::protocol_opt_to_enum(choice)), )), _ => Some(Msg::None), diff --git a/src/ui/activities/auth/components/form/s3.rs b/src/ui/activities/auth/components/form/s3.rs index 78a0468..ba8386c 100644 --- a/src/ui/activities/auth/components/form/s3.rs +++ b/src/ui/activities/auth/components/form/s3.rs @@ -1,10 +1,10 @@ use tuirealm::command::{Cmd, Direction}; +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent}; -use tuirealm::{Component, MockComponent}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3Bucket { component: Input, form_tab: FormTab, @@ -20,8 +20,11 @@ impl InputS3Bucket { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("my-bucket", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Bucket name", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "my-bucket", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Bucket name").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(bucket), form_tab, @@ -29,8 +32,8 @@ impl InputS3Bucket { } } -impl Component for InputS3Bucket { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3Bucket { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3BucketBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3BucketBlurDown)), @@ -45,7 +48,7 @@ impl Component for InputS3Bucket { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3Region { component: Input, form_tab: FormTab, @@ -61,8 +64,11 @@ impl InputS3Region { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("eu-west-1", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Region", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "eu-west-1", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Region").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(region), form_tab, @@ -70,8 +76,8 @@ impl InputS3Region { } } -impl Component for InputS3Region { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3Region { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3RegionBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3RegionBlurDown)), @@ -86,7 +92,7 @@ impl Component for InputS3Region { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3Endpoint { component: Input, form_tab: FormTab, @@ -102,11 +108,11 @@ impl InputS3Endpoint { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "http://localhost:9000", Style::default().fg(Color::Rgb(128, 128, 128)), - ) - .title("Endpoint", Alignment::Left) + )) + .title(Title::from("Endpoint").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(endpoint), form_tab, @@ -114,8 +120,8 @@ impl InputS3Endpoint { } } -impl Component for InputS3Endpoint { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3Endpoint { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3EndpointBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3EndpointBlurDown)), @@ -130,7 +136,7 @@ impl Component for InputS3Endpoint { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct RadioS3NewPathStyle { component: Radio, form_tab: FormTab, @@ -140,23 +146,27 @@ impl RadioS3NewPathStyle { pub fn new(new_path_style: bool, form_tab: FormTab, 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), ) .choices(["Yes", "No"]) - .foreground(color) .rewind(true) - .title("New path style", Alignment::Left) + .title(Title::from("New path style").alignment(HorizontalAlignment::Left)) .value(usize::from(!new_path_style)), form_tab, } } } -impl Component for RadioS3NewPathStyle { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for RadioS3NewPathStyle { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -199,7 +209,7 @@ impl Component for RadioS3NewPathStyle { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3Profile { component: Input, form_tab: FormTab, @@ -215,8 +225,11 @@ impl InputS3Profile { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("default", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Profile", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "default", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Profile").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(profile), form_tab, @@ -224,8 +237,8 @@ impl InputS3Profile { } } -impl Component for InputS3Profile { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3Profile { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3ProfileBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3ProfileBlurDown)), @@ -240,7 +253,7 @@ impl Component for InputS3Profile { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3AccessKey { component: Input, form_tab: FormTab, @@ -256,8 +269,11 @@ impl InputS3AccessKey { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder("AKIA...", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Access key", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "AKIA...", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Access key").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(access_key), form_tab, @@ -265,8 +281,8 @@ impl InputS3AccessKey { } } -impl Component for InputS3AccessKey { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3AccessKey { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3AccessKeyBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3AccessKeyBlurDown)), @@ -281,7 +297,7 @@ impl Component for InputS3AccessKey { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3SecretAccessKey { component: Input, form_tab: FormTab, @@ -297,7 +313,7 @@ impl InputS3SecretAccessKey { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Secret access key", Alignment::Left) + .title(Title::from("Secret access key").alignment(HorizontalAlignment::Left)) .input_type(InputType::Password('*')) .value(secret_access_key), form_tab, @@ -305,8 +321,8 @@ impl InputS3SecretAccessKey { } } -impl Component for InputS3SecretAccessKey { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3SecretAccessKey { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3SecretAccessKeyBlurDown)), FormTab::HostBridge => { @@ -325,7 +341,7 @@ impl Component for InputS3SecretAccessKey { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3SecurityToken { component: Input, form_tab: FormTab, @@ -341,7 +357,7 @@ impl InputS3SecurityToken { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Security token", Alignment::Left) + .title(Title::from("Security token").alignment(HorizontalAlignment::Left)) .input_type(InputType::Password('*')) .value(security_token), form_tab, @@ -349,8 +365,8 @@ impl InputS3SecurityToken { } } -impl Component for InputS3SecurityToken { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3SecurityToken { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3SecurityTokenBlurDown)), FormTab::HostBridge => { @@ -367,7 +383,7 @@ impl Component for InputS3SecurityToken { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct InputS3SessionToken { component: Input, form_tab: FormTab, @@ -383,7 +399,7 @@ impl InputS3SessionToken { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Session token", Alignment::Left) + .title(Title::from("Session token").alignment(HorizontalAlignment::Left)) .input_type(InputType::Password('*')) .value(session_token), form_tab, @@ -391,8 +407,8 @@ impl InputS3SessionToken { } } -impl Component for InputS3SessionToken { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputS3SessionToken { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3SessionTokenBlurDown)), FormTab::HostBridge => { diff --git a/src/ui/activities/auth/components/form/smb.rs b/src/ui/activities/auth/components/form/smb.rs index 1b91997..191d416 100644 --- a/src/ui/activities/auth/components/form/smb.rs +++ b/src/ui/activities/auth/components/form/smb.rs @@ -1,9 +1,9 @@ +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::NoUserEvent; -use tuirealm::{Component, MockComponent}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct InputSmbShare { component: Input, form_tab: FormTab, @@ -19,7 +19,7 @@ impl InputSmbShare { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Share", Alignment::Left) + .title(Title::from("Share").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(host), form_tab, @@ -27,8 +27,8 @@ impl InputSmbShare { } } -impl Component for InputSmbShare { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputSmbShare { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::SmbShareBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::SmbShareBlurDown)), @@ -44,7 +44,7 @@ impl Component for InputSmbShare { } #[cfg(posix)] -#[derive(MockComponent)] +#[derive(Component)] pub struct InputSmbWorkgroup { component: Input, form_tab: FormTab, @@ -61,7 +61,7 @@ impl InputSmbWorkgroup { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Workgroup", Alignment::Left) + .title(Title::from("Workgroup").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(host), form_tab, @@ -70,8 +70,8 @@ impl InputSmbWorkgroup { } #[cfg(posix)] -impl Component for InputSmbWorkgroup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputSmbWorkgroup { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::SmbWorkgroupDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::SmbWorkgroupDown)), diff --git a/src/ui/activities/auth/components/form/webdav.rs b/src/ui/activities/auth/components/form/webdav.rs index dc265f5..5e152fd 100644 --- a/src/ui/activities/auth/components/form/webdav.rs +++ b/src/ui/activities/auth/components/form/webdav.rs @@ -1,9 +1,9 @@ +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::NoUserEvent; -use tuirealm::{Component, MockComponent}; use super::*; -#[derive(MockComponent)] +#[derive(Component)] pub struct InputWebDAVUri { component: Input, form_tab: FormTab, @@ -19,11 +19,11 @@ impl InputWebDAVUri { .modifiers(BorderType::Rounded), ) .foreground(color) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "http://localhost:8080", Style::default().fg(Color::Rgb(128, 128, 128)), - ) - .title("HTTP url", Alignment::Left) + )) + .title(Title::from("HTTP url").alignment(HorizontalAlignment::Left)) .input_type(InputType::Text) .value(host), form_tab, @@ -31,8 +31,8 @@ impl InputWebDAVUri { } } -impl Component for InputWebDAVUri { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputWebDAVUri { + fn on(&mut self, ev: &Event) -> Option { let on_key_down = match self.form_tab { FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::WebDAVUriBlurDown)), FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::WebDAVUriBlurDown)), diff --git a/src/ui/activities/auth/components/popup.rs b/src/ui/activities/auth/components/popup.rs index 43b1b2d..8f33f03 100644 --- a/src/ui/activities/auth/components/popup.rs +++ b/src/ui/activities/auth/components/popup.rs @@ -2,17 +2,23 @@ //! //! auth activity popups -use tui_realm_stdlib::{List, Paragraph, Radio, Textarea}; +use tui_realm_stdlib::components::{List, Paragraph, Radio, Textarea}; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; -use tuirealm::event::{Key, KeyEvent, KeyModifiers}; -use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, 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, TableBuilder, + TextModifiers, Title, +}; +use tuirealm::ratatui::style::Stylize; +use tuirealm::ratatui::text::Text; +use tuirealm::state::{State, StateValue}; use super::{FormMsg, Msg, UiMsg}; // -- error popup -#[derive(MockComponent)] +#[derive(Component)] pub struct ErrorPopup { component: Paragraph, } @@ -21,21 +27,23 @@ impl ErrorPopup { pub fn new>(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 for ErrorPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ErrorPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, @@ -48,7 +56,7 @@ impl Component for ErrorPopup { // -- info popup -#[derive(MockComponent)] +#[derive(Component)] pub struct InfoPopup { component: Paragraph, } @@ -57,21 +65,23 @@ impl InfoPopup { pub fn new>(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 for InfoPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InfoPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, @@ -84,7 +94,7 @@ impl Component for InfoPopup { // -- wait popup -#[derive(MockComponent)] +#[derive(Component)] pub struct WaitPopup { component: Paragraph, } @@ -93,28 +103,30 @@ impl WaitPopup { pub fn new>(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 for WaitPopup { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for WaitPopup { + fn on(&mut self, _ev: &Event) -> Option { None } } // -- window size error -#[derive(MockComponent)] +#[derive(Component)] pub struct WindowSizeError { component: Paragraph, } @@ -123,30 +135,30 @@ impl WindowSizeError { pub fn new(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(Text::from_iter([SpanStatic::from( "termscp requires at least 24 lines of height to run", - )]) - .wrap(true), + )])) + .wrap_trim(true), } } } -impl Component for WindowSizeError { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for WindowSizeError { + fn on(&mut self, _ev: &Event) -> Option { None } } // -- quit popup -#[derive(MockComponent)] +#[derive(Component)] pub struct QuitPopup { component: Radio, } @@ -155,21 +167,25 @@ 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) - .title("Quit termscp?", Alignment::Center) + .title(Title::from("Quit termscp?").alignment(HorizontalAlignment::Center)) .rewind(true) .choices(["Yes", "No"]), } } } -impl Component for QuitPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for QuitPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseQuitPopup)) @@ -199,7 +215,7 @@ impl Component for QuitPopup { }) => { if matches!( self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) + CmdResult::Submit(State::Single(StateValue::Usize(0))) ) { Some(Msg::Form(FormMsg::Quit)) } else { @@ -213,7 +229,7 @@ impl Component for QuitPopup { // -- install update popup -#[derive(MockComponent)] +#[derive(Component)] pub struct InstallUpdatePopup { component: Radio, } @@ -222,21 +238,25 @@ impl InstallUpdatePopup { 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) - .title("Install update?", Alignment::Center) + .title(Title::from("Install update?").alignment(HorizontalAlignment::Center)) .rewind(true) .choices(["Yes", "No"]), } } } -impl Component for InstallUpdatePopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InstallUpdatePopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseInstallUpdatePopup)) @@ -266,7 +286,7 @@ impl Component for InstallUpdatePopup { }) => { if matches!( self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) + CmdResult::Submit(State::Single(StateValue::Usize(0))) ) { Some(Msg::Form(FormMsg::InstallUpdate)) } else { @@ -280,7 +300,7 @@ impl Component for InstallUpdatePopup { // -- release notes popup -#[derive(MockComponent)] +#[derive(Component)] pub struct ReleaseNotes { component: Textarea, } @@ -295,14 +315,14 @@ impl ReleaseNotes { .modifiers(BorderType::Rounded), ) .foreground(color) - .title("Release notes", Alignment::Center) - .text_rows(notes.lines().map(TextSpan::from)), + .title(Title::from("Release notes").alignment(HorizontalAlignment::Center)) + .text_rows(notes.lines().map(|l| SpanStatic::from(l.to_string()))), } } } -impl Component for ReleaseNotes { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ReleaseNotes { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, @@ -348,7 +368,7 @@ impl Component for ReleaseNotes { // -- keybindings popup -#[derive(MockComponent)] +#[derive(Component)] pub struct Keybindings { component: List, } @@ -362,43 +382,47 @@ impl Keybindings { .color(color) .modifiers(BorderType::Rounded), ) - .highlighted_str("? ") - .title("Keybindings", Alignment::Center) + .highlight_str("? ") + .title(Title::from("Keybindings").alignment(HorizontalAlignment::Center)) .scroll(true) .step(4) .rows( TableBuilder::default() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Quit termscp")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Quit termscp")) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Switch from form and bookmarks")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from( + " Switch from form and bookmarks", + )) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Switch bookmark tab")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Switch bookmark tab")) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Move up/down in current tab")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Move up/down in current tab")) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Connect/Load bookmark")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Connect/Load bookmark")) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Delete selected bookmark")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Delete selected bookmark")) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Enter setup")) + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Enter setup")) .add_row() - .add_col(TextSpan::new("").bold().fg(color)) - .add_col(TextSpan::from(" Save bookmark")) - .build(), + .add_col(SpanStatic::raw("").bold().fg(color)) + .add_col(SpanStatic::from(" Save bookmark")) + .build() + .into_iter() + .map(|row| row.into_iter().flat_map(|l| l.spans).collect::>()), ), } } } -impl Component for Keybindings { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for Keybindings { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, diff --git a/src/ui/activities/auth/components/text.rs b/src/ui/activities/auth/components/text.rs index 60bf2e8..dabd5fa 100644 --- a/src/ui/activities/auth/components/text.rs +++ b/src/ui/activities/auth/components/text.rs @@ -2,15 +2,17 @@ //! //! auth activity texts -use tui_realm_stdlib::{Label, Span}; -use tuirealm::props::{Color, TextModifiers, TextSpan}; -use tuirealm::{Component, Event, MockComponent, NoUserEvent}; +use tui_realm_stdlib::components::{Label, Span}; +use tuirealm::component::{AppComponent, Component}; +use tuirealm::event::{Event, NoUserEvent}; +use tuirealm::props::{Color, SpanStatic, TextModifiers}; +use tuirealm::ratatui::style::Stylize; use super::Msg; // -- Title -#[derive(MockComponent)] +#[derive(Component)] pub struct Title { component: Label, } @@ -25,15 +27,15 @@ impl Default for Title { } } -impl Component for Title { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for Title { + fn on(&mut self, _ev: &Event) -> Option { None } } // -- subtitle -#[derive(MockComponent)] +#[derive(Component)] pub struct Subtitle { component: Label, } @@ -48,15 +50,15 @@ impl Default for Subtitle { } } -impl Component for Subtitle { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for Subtitle { + fn on(&mut self, _ev: &Event) -> Option { None } } // -- new version disclaimer -#[derive(MockComponent)] +#[derive(Component)] pub struct NewVersionDisclaimer { component: Span, } @@ -65,9 +67,9 @@ impl NewVersionDisclaimer { pub fn new(new_version: &str, color: Color) -> Self { Self { component: Span::default().foreground(color).spans([ - TextSpan::from("termscp "), - TextSpan::new(new_version).underlined().bold(), - TextSpan::from( + SpanStatic::from("termscp "), + SpanStatic::raw(new_version.to_string()).underlined().bold(), + SpanStatic::from( " is NOW available! Install update and view release notes with ", ), ]), @@ -75,15 +77,15 @@ impl NewVersionDisclaimer { } } -impl Component for NewVersionDisclaimer { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for NewVersionDisclaimer { + fn on(&mut self, _ev: &Event) -> Option { None } } // -- HelpFooter -#[derive(MockComponent)] +#[derive(Component)] pub struct HelpFooter { component: Span, } @@ -92,27 +94,27 @@ impl HelpFooter { pub fn new(key_color: Color) -> Self { Self { component: Span::default().spans([ - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Help "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Enter setup "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Change field "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Switch tab "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Switch form "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Submit form "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Quit "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Help "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Enter setup "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Change field "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Switch tab "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Switch form "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Submit form "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Quit "), ]), } } } -impl Component for HelpFooter { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for HelpFooter { + fn on(&mut self, _ev: &Event) -> Option { None } } diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index 12bf27c..4b11934 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -2,15 +2,15 @@ //! //! Update impl -use tuirealm::{State, StateValue}; +use tuirealm::state::{State, StateValue}; use super::{ AuthActivity, AuthFormId, ExitReason, FormMsg, FormTab, HostBridgeProtocol, Id, InputMask, Msg, - UiAuthFormMsg, UiMsg, Update, + UiAuthFormMsg, UiMsg, }; -impl Update for AuthActivity { - fn update(&mut self, msg: Option) -> Option { +impl AuthActivity { + pub(super) fn update(&mut self, msg: Option) -> Option { self.redraw = true; match msg.unwrap_or(Msg::None) { Msg::Form(msg) => self.update_form(msg), @@ -49,7 +49,9 @@ impl AuthActivity { self.exit_reason = Some(super::ExitReason::Connect); } FormMsg::DeleteBookmark => { - if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::BookmarksList) { + if let Ok(State::Single(StateValue::Usize(idx))) = + self.app.state(&Id::BookmarksList) + { // Umount dialog self.umount_bookmark_del_dialog(); // Delete bookmark @@ -59,7 +61,8 @@ impl AuthActivity { } } FormMsg::DeleteRecent => { - if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::RecentsList) { + if let Ok(State::Single(StateValue::Usize(idx))) = self.app.state(&Id::RecentsList) + { // Umount dialog self.umount_recent_del_dialog(); // Delete recent diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index 7da7e36..e771190 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -5,6 +5,7 @@ use tuirealm::props::Color; use tuirealm::ratatui::layout::{Constraint, Direction, Layout}; use tuirealm::ratatui::widgets::Clear; +use tuirealm::terminal::TerminalAdapter; use super::{ AuthActivity, AuthFormId, Context, FileTransferProtocol, FormTab, HostBridgeProtocol, Id, diff --git a/src/ui/activities/auth/view/query.rs b/src/ui/activities/auth/view/query.rs index e89ef4f..b9509e8 100644 --- a/src/ui/activities/auth/view/query.rs +++ b/src/ui/activities/auth/view/query.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use std::str::FromStr; -use tuirealm::{State, StateValue}; +use tuirealm::state::{State, StateValue}; use super::*; use crate::filetransfer::FileTransferParams; @@ -123,7 +123,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::RemoteDirectory)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => { + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => { Some(PathBuf::from(x.as_str())) } _ => None, @@ -138,7 +138,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::LocalDirectory)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => { + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => { Some(PathBuf::from(x.as_str())) } _ => None, @@ -150,7 +150,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::WebDAVUri)) { - Ok(State::One(StateValue::String(x))) => x, + Ok(State::Single(StateValue::String(x))) => x, _ => String::new(), } } @@ -160,7 +160,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::Address)) { - Ok(State::One(StateValue::String(x))) => x, + Ok(State::Single(StateValue::String(x))) => x, _ => String::new(), } } @@ -170,7 +170,9 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::Port)) { - Ok(State::One(StateValue::String(x))) => u16::from_str(x.as_str()).unwrap_or_default(), + Ok(State::Single(StateValue::String(x))) => { + u16::from_str(x.as_str()).unwrap_or_default() + } _ => 0, } } @@ -183,7 +185,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::Username)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -196,7 +198,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::Password)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -206,7 +208,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3Bucket)) { - Ok(State::One(StateValue::String(x))) => x, + Ok(State::Single(StateValue::String(x))) => x, _ => String::new(), } } @@ -219,7 +221,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3Region)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -232,7 +234,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3Endpoint)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -245,7 +247,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3Profile)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -258,7 +260,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3AccessKey)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -271,7 +273,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3SecretAccessKey)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -284,7 +286,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3SecurityToken)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -297,7 +299,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::S3SessionToken)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -309,7 +311,7 @@ impl AuthActivity { matches!( self.app .state(&Self::form_tab_id(form_tab, AuthFormId::S3NewPathStyle)), - Ok(State::One(StateValue::Usize(0))) + Ok(State::Single(StateValue::Usize(0))) ) } @@ -321,7 +323,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::KubeNamespace)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -334,7 +336,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::KubeClusterUrl)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -347,7 +349,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::KubeUsername)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -360,7 +362,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::KubeClientCert)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -373,7 +375,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::KubeClientKey)) { - Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x), + Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x), _ => None, } } @@ -383,7 +385,7 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::SmbShare)) { - Ok(State::One(StateValue::String(x))) => x, + Ok(State::Single(StateValue::String(x))) => x, _ => String::new(), } } @@ -397,19 +399,19 @@ impl AuthActivity { .app .state(&Self::form_tab_id(form_tab, AuthFormId::SmbWorkgroup)) { - Ok(State::One(StateValue::String(x))) => Some(x), + Ok(State::Single(StateValue::String(x))) => Some(x), _ => None, } } pub(in crate::ui::activities::auth) fn get_new_bookmark(&self) -> (String, bool) { let name = match self.app.state(&Id::BookmarkName) { - Ok(State::One(StateValue::String(name))) => name, + Ok(State::Single(StateValue::String(name))) => name, _ => String::default(), }; if matches!( self.app.state(&Id::BookmarkSavePassword), - Ok(State::One(StateValue::Usize(0))) + Ok(State::Single(StateValue::Usize(0))) ) { (name, true) } else { diff --git a/src/ui/activities/auth/view/visible.rs b/src/ui/activities/auth/view/visible.rs index 6ddb4e5..fa37d73 100644 --- a/src/ui/activities/auth/view/visible.rs +++ b/src/ui/activities/auth/view/visible.rs @@ -1,4 +1,4 @@ -use tuirealm::{Sub, SubClause, SubEventClause}; +use tuirealm::subscription::{EventClause, Sub, SubClause}; use super::*; @@ -435,55 +435,55 @@ impl AuthActivity { Box::::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::Function(10), modifiers: KeyModifiers::NONE, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Char('c'), modifiers: KeyModifiers::CONTROL, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Char('h'), modifiers: KeyModifiers::CONTROL, }), 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::Char('r'), modifiers: KeyModifiers::CONTROL, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Char('s'), modifiers: KeyModifiers::CONTROL, }), Self::no_popup_mounted_clause(), ), - Sub::new(SubEventClause::WindowResize, SubClause::Always), + Sub::new(EventClause::WindowResize, SubClause::Always), ], ) { error!("Failed to mount component: {err}"); diff --git a/src/ui/activities/filetransfer.rs b/src/ui/activities/filetransfer.rs index e2bfe1f..987c970 100644 --- a/src/ui/activities/filetransfer.rs +++ b/src/ui/activities/filetransfer.rs @@ -26,7 +26,10 @@ use lib::transfer::{TransferOpts, TransferStates}; use lib::walkdir::WalkdirStates; use session::TransferPayload; use tempfile::TempDir; -use tuirealm::{Application, EventListenerCfg, NoUserEvent}; +use tuirealm::application::Application; +use tuirealm::event::NoUserEvent; +use tuirealm::listener::EventListenerCfg; +use tuirealm::terminal::TerminalAdapter; use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason}; use crate::config::themes::Theme; @@ -286,9 +289,7 @@ impl FileTransferActivity { exit_reason: None, context: None, app: Application::init( - EventListenerCfg::default() - .poll_timeout(ticks) - .crossterm_input_listener(ticks, CROSSTERM_MAX_POLL), + EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL), ), redraw: true, browser: Browser::new(local_pane, remote_pane), diff --git a/src/ui/activities/filetransfer/actions.rs b/src/ui/activities/filetransfer/actions.rs index 222e7bf..8d46c08 100644 --- a/src/ui/activities/filetransfer/actions.rs +++ b/src/ui/activities/filetransfer/actions.rs @@ -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, } } diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index 82d2803..7a6ca15 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -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}; diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index 9f91a92..f81ded1 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -6,6 +6,8 @@ // ext use std::path::{Path, PathBuf}; +use tuirealm::terminal::TerminalAdapter; + use super::{File, FileTransferActivity, LogLevel, TransferPayload}; impl FileTransferActivity { diff --git a/src/ui/activities/filetransfer/actions/pending.rs b/src/ui/activities/filetransfer/actions/pending.rs index 1dc1bdb..9451325 100644 --- a/src/ui/activities/filetransfer/actions/pending.rs +++ b/src/ui/activities/filetransfer/actions/pending.rs @@ -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; diff --git a/src/ui/activities/filetransfer/components.rs b/src/ui/activities/filetransfer/components.rs index a13b727..7aee40c 100644 --- a/src/ui/activities/filetransfer/components.rs +++ b/src/ui/activities/filetransfer/components.rs @@ -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 for GlobalListener { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for GlobalListener { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::ShowDisconnectPopup)) diff --git a/src/ui/activities/filetransfer/components/log.rs b/src/ui/activities/filetransfer/components/log.rs index fc4e770..ba6e114 100644 --- a/src/ui/activities/filetransfer/components/log.rs +++ b/src/ui/activities/filetransfer/components/log.rs @@ -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 = self .props .get(Attribute::Content) - .unwrap() - .unwrap_table() - .iter() - .map(|row| { - let row_refs = row.iter().collect::>(); - 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::>(); + 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 { - self.props.get(attr) + fn query<'a>(&'a self, attr: Attribute) -> Option> { + 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 for Log { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for Log { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Up, .. }) => { self.perform(Cmd::Move(Direction::Down)); diff --git a/src/ui/activities/filetransfer/components/misc.rs b/src/ui/activities/filetransfer/components/misc.rs index de22e13..99b04af 100644 --- a/src/ui/activities/filetransfer/components/misc.rs +++ b/src/ui/activities/filetransfer/components/misc.rs @@ -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("").bold().fg(key_color), - TextSpan::from(" Help "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Change tab "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Transfer "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Enter dir "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Save as "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" View "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Edit "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Copy "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Rename "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Make dir "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Delete "), - TextSpan::from("").bold().fg(key_color), - TextSpan::from(" Quit "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Help "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Change tab "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Transfer "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Enter dir "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Save as "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" View "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Edit "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Copy "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Rename "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Make dir "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Delete "), + SpanStatic::from("").bold().fg(key_color), + SpanStatic::from(" Quit "), ]), } } } -impl Component for FooterBar { - fn on(&mut self, _: Event) -> Option { +impl AppComponent for FooterBar { + fn on(&mut self, _: &Event) -> Option { None } } diff --git a/src/ui/activities/filetransfer/components/popups/chmod.rs b/src/ui/activities/filetransfer/components/popups/chmod.rs index 91f037f..477eae3 100644 --- a/src/ui/activities/filetransfer/components/popups/chmod.rs +++ b/src/ui/activities/filetransfer/components/popups/chmod.rs @@ -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 { - self.props.get(attr) + fn query<'a>( + &'a self, + attr: tuirealm::props::Attribute, + ) -> Option> { + 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 for ChmodPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ChmodPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseChmodPopup)) diff --git a/src/ui/activities/filetransfer/components/popups/copy.rs b/src/ui/activities/filetransfer/components/popups/copy.rs index b081c9d..145e440 100644 --- a/src/ui/activities/filetransfer/components/popups/copy.rs +++ b/src/ui/activities/filetransfer/components/popups/copy.rs @@ -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 for CopyPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for CopyPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -73,13 +76,13 @@ impl Component 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), diff --git a/src/ui/activities/filetransfer/components/popups/delete.rs b/src/ui/activities/filetransfer/components/popups/delete.rs index cef6bdb..0e13318 100644 --- a/src/ui/activities/filetransfer/components/popups/delete.rs +++ b/src/ui/activities/filetransfer/components/popups/delete.rs @@ -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 for DeletePopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for DeletePopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -59,7 +66,7 @@ impl Component 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 { diff --git a/src/ui/activities/filetransfer/components/popups/disconnect.rs b/src/ui/activities/filetransfer/components/popups/disconnect.rs index be07732..7777343 100644 --- a/src/ui/activities/filetransfer/components/popups/disconnect.rs +++ b/src/ui/activities/filetransfer/components/popups/disconnect.rs @@ -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 for DisconnectPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for DisconnectPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -58,7 +68,7 @@ impl Component 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 { diff --git a/src/ui/activities/filetransfer/components/popups/error.rs b/src/ui/activities/filetransfer/components/popups/error.rs index 20444cf..c1c0f92 100644 --- a/src/ui/activities/filetransfer/components/popups/error.rs +++ b/src/ui/activities/filetransfer/components/popups/error.rs @@ -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>(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 for ErrorPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ErrorPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, @@ -39,7 +42,7 @@ impl Component for ErrorPopup { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct FatalPopup { component: Paragraph, } @@ -48,21 +51,23 @@ impl FatalPopup { pub fn new>(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 for FatalPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for FatalPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, diff --git a/src/ui/activities/filetransfer/components/popups/file_info.rs b/src/ui/activities/filetransfer/components/popups/file_info.rs index 9328674..18980aa 100644 --- a/src/ui/activities/filetransfer/components/popups/file_info.rs +++ b/src/ui/activities/filetransfer/components/popups/file_info.rs @@ -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::>()), + ), } } } -impl Component for FileInfoPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for FileInfoPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, diff --git a/src/ui/activities/filetransfer/components/popups/filter.rs b/src/ui/activities/filetransfer/components/popups/filter.rs index 0fd39ae..ced085f 100644 --- a/src/ui/activities/filetransfer/components/popups/filter.rs +++ b/src/ui/activities/filetransfer/components/popups/filter.rs @@ -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 for FilterPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for FilterPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -76,13 +77,15 @@ impl Component 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, .. }) => { diff --git a/src/ui/activities/filetransfer/components/popups/goto.rs b/src/ui/activities/filetransfer/components/popups/goto.rs index 4837156..d5087c3 100644 --- a/src/ui/activities/filetransfer/components/popups/goto.rs +++ b/src/ui/activities/filetransfer/components/popups/goto.rs @@ -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 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 for Suggestion { impl From 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 for autocompletion)", Alignment::Center), + )) + .title( + Title::from("Go to… (Press 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 { + fn query<'a>(&'a self, attr: Attribute) -> Option> { 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 for GotoPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for GotoPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -298,7 +308,7 @@ impl Component 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 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, .. }) => { diff --git a/src/ui/activities/filetransfer/components/popups/keybindings.rs b/src/ui/activities/filetransfer/components/popups/keybindings.rs index 72ff900..6c31e09 100644 --- a/src/ui/activities/filetransfer/components/popups/keybindings.rs +++ b/src/ui/activities/filetransfer/components/popups/keybindings.rs @@ -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("").bold().fg(key_color)) - .add_col(TextSpan::from(" Disconnect")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Disconnect")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Go to previous directory")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Go to previous directory")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Change explorer tab")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Change explorer tab")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Move up/down in list")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Move up/down in list")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Enter directory")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Enter directory")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Upload/Download file")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Upload/Download file")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Switch between explorer and log window", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Toggle hidden files")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Toggle hidden files")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Change file sorting mode")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Change file sorting mode")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Copy")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Copy")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Make directory")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Make directory")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Search files")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Search files")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Go to path")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Go to path")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Show help")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Show help")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Show info about selected file", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Create symlink pointing to the current selected entry", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Reload directory content")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Reload directory content")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Select file")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Select file")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Create new file")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Create new file")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Open text file with preferred editor", )) .add_row() - .add_col(TextSpan::new("

").bold().fg(key_color)) - .add_col(TextSpan::from(" Toggle bottom panel")) + .add_col(SpanStatic::raw("

").bold().fg(key_color)) + .add_col(SpanStatic::from(" Toggle bottom panel")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Quit termscp")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Quit termscp")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Rename file")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Rename file")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Save file as")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Save file as")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Watch/unwatch file changes")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( + " Watch/unwatch file changes", + )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Go to parent directory")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Go to parent directory")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Open file with default application for file type", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Open file with specified application", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Execute shell command")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Execute shell command")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Toggle synchronized browsing", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Change file permissions")) + .add_col(SpanStatic::raw("").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("").bold().fg(key_color)) - .add_col(TextSpan::from(" Delete selected file")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Delete selected file")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Select all files")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Select all files")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Deselect all files")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Deselect all files")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Interrupt file transfer")) + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from(" Interrupt file transfer")) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from( + .add_col(SpanStatic::raw("").bold().fg(key_color)) + .add_col(SpanStatic::from( " Get total path size of selected files", )) .add_row() - .add_col(TextSpan::new("").bold().fg(key_color)) - .add_col(TextSpan::from(" Show watched paths")) - .build(), + .add_col(SpanStatic::raw("").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::>()), ), } } } -impl Component for KeybindingsPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for KeybindingsPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, diff --git a/src/ui/activities/filetransfer/components/popups/mkdir.rs b/src/ui/activities/filetransfer/components/popups/mkdir.rs index 7124181..66ce1ca 100644 --- a/src/ui/activities/filetransfer/components/popups/mkdir.rs +++ b/src/ui/activities/filetransfer/components/popups/mkdir.rs @@ -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 for MkdirPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MkdirPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -73,13 +76,13 @@ impl Component 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 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 for SyncBrowsingMkdirPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SyncBrowsingMkdirPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -149,7 +152,7 @@ impl Component 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 { diff --git a/src/ui/activities/filetransfer/components/popups/newfile.rs b/src/ui/activities/filetransfer/components/popups/newfile.rs index d32da4b..e039f4e 100644 --- a/src/ui/activities/filetransfer/components/popups/newfile.rs +++ b/src/ui/activities/filetransfer/components/popups/newfile.rs @@ -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 for NewfilePopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for NewfilePopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -73,13 +74,15 @@ impl Component 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, .. }) => { diff --git a/src/ui/activities/filetransfer/components/popups/open_with.rs b/src/ui/activities/filetransfer/components/popups/open_with.rs index e228dcc..5a7360e 100644 --- a/src/ui/activities/filetransfer/components/popups/open_with.rs +++ b/src/ui/activities/filetransfer/components/popups/open_with.rs @@ -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 for OpenWithPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for OpenWithPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -73,13 +77,13 @@ impl Component 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), diff --git a/src/ui/activities/filetransfer/components/popups/progress_bar.rs b/src/ui/activities/filetransfer/components/popups/progress_bar.rs index 0c92d65..3359427 100644 --- a/src/ui/activities/filetransfer/components/popups/progress_bar.rs +++ b/src/ui/activities/filetransfer/components/popups/progress_bar.rs @@ -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>(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 for TransferProgressBar { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for TransferProgressBar { + fn on(&mut self, ev: &Event) -> Option { if matches!( ev, Event::Keyboard(KeyEvent { diff --git a/src/ui/activities/filetransfer/components/popups/quit.rs b/src/ui/activities/filetransfer/components/popups/quit.rs index ad8fa95..cfde5bf 100644 --- a/src/ui/activities/filetransfer/components/popups/quit.rs +++ b/src/ui/activities/filetransfer/components/popups/quit.rs @@ -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 for QuitPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for QuitPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -58,7 +68,7 @@ impl Component 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 { diff --git a/src/ui/activities/filetransfer/components/popups/rename.rs b/src/ui/activities/filetransfer/components/popups/rename.rs index f73f301..68b3e3a 100644 --- a/src/ui/activities/filetransfer/components/popups/rename.rs +++ b/src/ui/activities/filetransfer/components/popups/rename.rs @@ -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 for RenamePopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for RenamePopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -73,13 +76,13 @@ impl Component 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), diff --git a/src/ui/activities/filetransfer/components/popups/replace.rs b/src/ui/activities/filetransfer/components/popups/replace.rs index 7114f26..cd0924f 100644 --- a/src/ui/activities/filetransfer/components/popups/replace.rs +++ b/src/ui/activities/filetransfer/components/popups/replace.rs @@ -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 for ReplacePopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ReplacePopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -60,19 +67,19 @@ impl Component 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), diff --git a/src/ui/activities/filetransfer/components/popups/save_as.rs b/src/ui/activities/filetransfer/components/popups/save_as.rs index 63a84e2..6ecfcfc 100644 --- a/src/ui/activities/filetransfer/components/popups/save_as.rs +++ b/src/ui/activities/filetransfer/components/popups/save_as.rs @@ -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 for SaveAsPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SaveAsPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -73,13 +74,13 @@ impl Component 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), diff --git a/src/ui/activities/filetransfer/components/popups/sorting.rs b/src/ui/activities/filetransfer/components/popups/sorting.rs index 7d60e26..f3427a2 100644 --- a/src/ui/activities/filetransfer/components/popups/sorting.rs +++ b/src/ui/activities/filetransfer/components/popups/sorting.rs @@ -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 for SortingPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SortingPopup { + fn on(&mut self, ev: &Event) -> Option { let result = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -50,7 +57,7 @@ impl Component 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, diff --git a/src/ui/activities/filetransfer/components/popups/status_bar.rs b/src/ui/activities/filetransfer/components/popups/status_bar.rs index f2b0a8c..eaa23da 100644 --- a/src/ui/activities/filetransfer/components/popups/status_bar.rs +++ b/src/ui/activities/filetransfer/components/popups/status_bar.rs @@ -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 for StatusBarLocal { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for StatusBarLocal { + fn on(&mut self, _ev: &Event) -> Option { 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 for StatusBarRemote { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for StatusBarRemote { + fn on(&mut self, _ev: &Event) -> Option { None } } diff --git a/src/ui/activities/filetransfer/components/popups/symlink.rs b/src/ui/activities/filetransfer/components/popups/symlink.rs index 07b8f85..4a5ad12 100644 --- a/src/ui/activities/filetransfer/components/popups/symlink.rs +++ b/src/ui/activities/filetransfer/components/popups/symlink.rs @@ -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 for SymlinkPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SymlinkPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -76,13 +77,13 @@ impl Component 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), diff --git a/src/ui/activities/filetransfer/components/popups/wait.rs b/src/ui/activities/filetransfer/components/popups/wait.rs index cb72ad8..9972b15 100644 --- a/src/ui/activities/filetransfer/components/popups/wait.rs +++ b/src/ui/activities/filetransfer/components/popups/wait.rs @@ -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>(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 for WaitPopup { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for WaitPopup { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct WalkdirWaitPopup { component: Paragraph, } @@ -42,24 +45,24 @@ impl WalkdirWaitPopup { pub fn new>(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 for WalkdirWaitPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for WalkdirWaitPopup { + fn on(&mut self, ev: &Event) -> Option { if matches!( ev, Event::Keyboard(KeyEvent { diff --git a/src/ui/activities/filetransfer/components/popups/watcher.rs b/src/ui/activities/filetransfer/components/popups/watcher.rs index bf5e6f1..671f6b8 100644 --- a/src/ui/activities/filetransfer/components/popups/watcher.rs +++ b/src/ui/activities/filetransfer/components/popups/watcher.rs @@ -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::>>(), ), } } } -impl Component for WatchedPathsList { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for WatchedPathsList { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseWatchedPathsList)) @@ -82,7 +85,7 @@ impl Component 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 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 for WatcherPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for WatcherPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -149,7 +156,7 @@ impl Component 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 { diff --git a/src/ui/activities/filetransfer/components/selected_files.rs b/src/ui/activities/filetransfer/components/selected_files.rs index 57f032d..4207889 100644 --- a/src/ui/activities/filetransfer/components/selected_files.rs +++ b/src/ui/activities/filetransfer/components/selected_files.rs @@ -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, @@ -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::>>(), ), } } @@ -61,8 +62,8 @@ impl SelectedFilesList { } } -impl Component for SelectedFilesList { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SelectedFilesList { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -112,7 +113,7 @@ impl Component 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; }; diff --git a/src/ui/activities/filetransfer/components/terminal.rs b/src/ui/activities/filetransfer/components/terminal.rs index 1a80ad4..68753cd 100644 --- a/src/ui/activities/filetransfer/components/terminal.rs +++ b/src/ui/activities/filetransfer/components/terminal.rs @@ -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 for Terminal { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for Terminal { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ui(UiMsg::CloseExecPopup)) @@ -55,7 +55,7 @@ impl Component 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 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, diff --git a/src/ui/activities/filetransfer/components/terminal/component.rs b/src/ui/activities/filetransfer/components/terminal/component.rs index e06c6d9..059f5f1 100644 --- a/src/ui/activities/filetransfer/components/terminal/component.rs +++ b/src/ui/activities/filetransfer/components/terminal/component.rs @@ -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 { - self.props.get(attr) + fn query<'a>(&'a self, attr: Attribute) -> Option> { + 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, } } } diff --git a/src/ui/activities/filetransfer/components/transfer.rs b/src/ui/activities/filetransfer/components/transfer.rs index 29eb61a..0bef20e 100644 --- a/src/ui/activities/filetransfer/components/transfer.rs +++ b/src/ui/activities/filetransfer/components/transfer.rs @@ -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) -> Option { + fn on_search(&mut self, ev: &Event) -> Option { 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) -> Option { + fn on_file_list(&mut self, ev: &Event) -> Option { 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 for ExplorerFuzzy { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerFuzzy { + fn on(&mut self, ev: &Event) -> Option { 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 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 for ExplorerFind { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerFind { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -299,7 +318,7 @@ impl Component 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 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 for ExplorerLocal { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerLocal { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -441,7 +469,7 @@ impl Component 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 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 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 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 for ExplorerRemote { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerRemote { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -659,7 +696,7 @@ impl Component 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 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 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)) diff --git a/src/ui/activities/filetransfer/components/transfer/file_list.rs b/src/ui/activities/filetransfer/components/transfer/file_list.rs index 9cdc1d3..73a8cbe 100644 --- a/src/ui/activities/filetransfer/components/transfer/file_list.rs +++ b/src/ui/activities/filetransfer/components/transfer/file_list.rs @@ -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>(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 = 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 = 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 { - self.props.get(attr) + fn query<'a>(&'a self, attr: Attribute) -> Option> { + 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, } } } diff --git a/src/ui/activities/filetransfer/components/transfer/file_list_with_search.rs b/src/ui/activities/filetransfer/components/transfer/file_list_with_search.rs index 326528d..f54c69d 100644 --- a/src/ui/activities/filetransfer/components/transfer/file_list_with_search.rs +++ b/src/ui/activities/filetransfer/components/transfer/file_list_with_search.rs @@ -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>(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 { + fn query<'a>(&'a self, attr: Attribute) -> Option> { 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), diff --git a/src/ui/activities/filetransfer/misc.rs b/src/ui/activities/filetransfer/misc.rs index 435a210..da7f193 100644 --- a/src/ui/activities/filetransfer/misc.rs +++ b/src/ui/activities/filetransfer/misc.rs @@ -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; diff --git a/src/ui/activities/filetransfer/misc/filelist.rs b/src/ui/activities/filetransfer/misc/filelist.rs index 0cb89e3..ee404d0 100644 --- a/src/ui/activities/filetransfer/misc/filelist.rs +++ b/src/ui/activities/filetransfer/misc/filelist.rs @@ -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> = self + let files: Vec> = 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> = self + let files: Vec> = 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> = self + let files: Vec> = 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( diff --git a/src/ui/activities/filetransfer/misc/log.rs b/src/ui/activities/filetransfer/misc/log.rs index 7bb381b..c8267a2 100644 --- a/src/ui/activities/filetransfer/misc/log.rs +++ b/src/ui/activities/filetransfer/misc/log.rs @@ -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, diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 27137b1..97f7e14 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -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 for FileTransferActivity { - fn update(&mut self, msg: Option) -> Option { +impl FileTransferActivity { + pub(super) fn update(&mut self, msg: Option) -> Option { 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)) => { diff --git a/src/ui/activities/filetransfer/view/layout.rs b/src/ui/activities/filetransfer/view/layout.rs index e445191..d830517 100644 --- a/src/ui/activities/filetransfer/view/layout.rs +++ b/src/ui/activities/filetransfer/view/layout.rs @@ -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::>() + .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::>() .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::::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), ], )); } diff --git a/src/ui/activities/filetransfer/view/popups.rs b/src/ui/activities/filetransfer/view/popups.rs index 39104ee..55ea243 100644 --- a/src/ui/activities/filetransfer/view/popups.rs +++ b/src/ui/activities/filetransfer/view/popups.rs @@ -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"), ])), ); diff --git a/src/ui/activities/setup.rs b/src/ui/activities/setup.rs index 8fcf1ad..c87e40b 100644 --- a/src/ui/activities/setup.rs +++ b/src/ui/activities/setup.rs @@ -14,10 +14,11 @@ mod view; // Ext use std::time::Duration; -use tuirealm::application::PollStrategy; +use tuirealm::application::{Application, PollStrategy}; +use tuirealm::event::NoUserEvent; use tuirealm::listener::EventListenerCfg; use tuirealm::props::Color; -use tuirealm::{Application, NoUserEvent, Update}; +use tuirealm::terminal::TerminalAdapter; use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason}; use crate::config::themes::Theme; @@ -258,9 +259,7 @@ impl SetupActivity { pub fn new(ticks: Duration) -> Self { Self { app: Application::init( - EventListenerCfg::default() - .crossterm_input_listener(ticks, CROSSTERM_MAX_POLL) - .poll_timeout(ticks), + EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL), ), exit_reason: None, context: None, @@ -347,7 +346,10 @@ impl Activity for SetupActivity { if self.context.is_none() { return; } - match self.app.tick(PollStrategy::UpTo(3)) { + match self + .app + .tick(PollStrategy::UpTo(3, std::time::Duration::from_millis(10))) + { Ok(messages) => { if !messages.is_empty() { self.redraw = true; diff --git a/src/ui/activities/setup/actions.rs b/src/ui/activities/setup/actions.rs index 76acd85..9647364 100644 --- a/src/ui/activities/setup/actions.rs +++ b/src/ui/activities/setup/actions.rs @@ -7,7 +7,8 @@ use std::env; use tuirealm::ratatui::style::Color; -use tuirealm::{State, StateValue}; +use tuirealm::state::{State, StateValue}; +use tuirealm::terminal::TerminalAdapter; use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout}; // Ext @@ -96,7 +97,7 @@ impl SetupActivity { // Get key // get index let idx: Option = match self.app.state(&Id::Ssh(IdSsh::SshKeys)) { - Ok(State::One(StateValue::Usize(idx))) => Some(idx), + Ok(State::Single(StateValue::Usize(idx))) => Some(idx), _ => None, }; // get ssh key and delete it @@ -114,11 +115,11 @@ impl SetupActivity { pub(super) fn action_new_ssh_key(&mut self) -> Result<(), String> { // get parameters let host: String = match self.app.state(&Id::Ssh(IdSsh::SshHost)) { - Ok(State::One(StateValue::String(host))) => host, + Ok(State::Single(StateValue::String(host))) => host, _ => String::new(), }; let username: String = match self.app.state(&Id::Ssh(IdSsh::SshUsername)) { - Ok(State::One(StateValue::String(user))) => user, + Ok(State::Single(StateValue::String(user))) => user, _ => String::new(), }; // Prepare text editor @@ -382,7 +383,7 @@ impl SetupActivity { /// Get color from component fn get_color(&self, component: &Id) -> Result { match self.app.state(component) { - Ok(State::One(StateValue::String(color))) => { + Ok(State::Single(StateValue::String(color))) => { match crate::utils::parser::parse_color(color.as_str()) { Some(c) => Ok(c), None => Err(()), diff --git a/src/ui/activities/setup/components.rs b/src/ui/activities/setup/components.rs index 144d144..91c0c5f 100644 --- a/src/ui/activities/setup/components.rs +++ b/src/ui/activities/setup/components.rs @@ -16,19 +16,19 @@ pub(super) use config::{ }; pub(super) use ssh::{DelSshKeyPopup, SshHost, SshKeys, SshUsername}; pub(super) use theme::*; -use tui_realm_stdlib::Phantom; +use tui_realm_stdlib::components::Phantom; +use tuirealm::component::{AppComponent, Component}; use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent}; -use tuirealm::{Component, MockComponent}; // -- global listener -#[derive(Default, MockComponent)] +#[derive(Default, Component)] pub struct GlobalListener { component: Phantom, } -impl Component for GlobalListener { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for GlobalListener { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Function(10), diff --git a/src/ui/activities/setup/components/commons.rs b/src/ui/activities/setup/components/commons.rs index e277e60..9c9ebe5 100644 --- a/src/ui/activities/setup/components/commons.rs +++ b/src/ui/activities/setup/components/commons.rs @@ -2,15 +2,21 @@ //! //! config tab components -use tui_realm_stdlib::{List, Paragraph, Radio, Span}; +use tui_realm_stdlib::components::{List, Paragraph, Radio, Span}; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; -use tuirealm::event::{Key, KeyEvent, KeyModifiers}; -use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, 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::{ + BorderSides, BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, TableBuilder, + TextModifiers, Title, +}; +use tuirealm::ratatui::style::Stylize; +use tuirealm::ratatui::text::Text; +use tuirealm::state::{State, StateValue}; use super::{CommonMsg, Msg, ViewLayout}; -#[derive(MockComponent)] +#[derive(Component)] pub struct ErrorPopup { component: Paragraph, } @@ -19,21 +25,23 @@ impl ErrorPopup { pub fn new>(text: S) -> Self { Self { component: Paragraph::default() - .alignment(Alignment::Center) + .alignment_horizontal(HorizontalAlignment::Center) .borders( Borders::default() .color(Color::Red) .modifiers(BorderType::Rounded), ) .foreground(Color::Red) - .text([TextSpan::from(text.as_ref())]) - .wrap(true), + .text(Text::from_iter([SpanStatic::from( + text.as_ref().to_string(), + )])) + .wrap_trim(true), } } } -impl Component for ErrorPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ErrorPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, @@ -44,7 +52,7 @@ impl Component for ErrorPopup { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct Footer { component: Span, } @@ -53,28 +61,28 @@ impl Default for Footer { fn default() -> Self { Self { component: Span::default().spans([ - TextSpan::new("").bold().fg(Color::Cyan), - TextSpan::new(" Help "), - TextSpan::new("").bold().fg(Color::Cyan), - TextSpan::new(" Save parameters "), - TextSpan::new("").bold().fg(Color::Cyan), - TextSpan::new(" Exit "), - TextSpan::new("").bold().fg(Color::Cyan), - TextSpan::new(" Change panel "), - TextSpan::new("").bold().fg(Color::Cyan), - TextSpan::new(" Change field "), + SpanStatic::raw("").bold().fg(Color::Cyan), + SpanStatic::raw(" Help "), + SpanStatic::raw("").bold().fg(Color::Cyan), + SpanStatic::raw(" Save parameters "), + SpanStatic::raw("").bold().fg(Color::Cyan), + SpanStatic::raw(" Exit "), + SpanStatic::raw("").bold().fg(Color::Cyan), + SpanStatic::raw(" Change panel "), + SpanStatic::raw("").bold().fg(Color::Cyan), + SpanStatic::raw(" Change field "), ]), } } } -impl Component for Footer { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for Footer { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct Header { component: Radio, } @@ -83,13 +91,17 @@ impl Header { pub fn new(layout: ViewLayout) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::Yellow) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::Yellow) .sides(BorderSides::BOTTOM), ) .choices(["Configuration parameters", "SSH Keys", "Theme"]) - .foreground(Color::Yellow) .value(match layout { ViewLayout::SetupForm => 0, ViewLayout::SshKeys => 1, @@ -99,13 +111,13 @@ impl Header { } } -impl Component for Header { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for Header { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct Keybindings { component: List, } @@ -115,45 +127,47 @@ impl Default for Keybindings { Self { component: List::default() .borders(Borders::default().modifiers(BorderType::Rounded)) - .title("Keybindings", Alignment::Center) + .title(Title::from("Keybindings").alignment(HorizontalAlignment::Center)) .scroll(true) - .highlighted_str("? ") + .highlight_str("? ") .rows( TableBuilder::default() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Exit setup")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Exit setup")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Change setup page")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Change setup page")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Change cursor")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Change cursor")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Change input field")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Change input field")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Select / Dismiss popup")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Select / Dismiss popup")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Delete SSH key")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Delete SSH key")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" New SSH key")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" New SSH key")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Revert changes")) + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Revert changes")) .add_row() - .add_col(TextSpan::new("").bold().fg(Color::Cyan)) - .add_col(TextSpan::from(" Save configuration")) - .build(), + .add_col(SpanStatic::raw("").bold().fg(Color::Cyan)) + .add_col(SpanStatic::from(" Save configuration")) + .build() + .into_iter() + .map(|row| row.into_iter().flat_map(|l| l.spans).collect::>()), ), } } } -impl Component for Keybindings { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for Keybindings { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc | Key::Enter, @@ -197,7 +211,7 @@ impl Component for Keybindings { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct QuitPopup { component: Radio, } @@ -206,15 +220,19 @@ impl Default for QuitPopup { fn default() -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::Red) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::Red) .modifiers(BorderType::Rounded), ) - .foreground(Color::Red) .title( - "There are unsaved changes! Save changes before leaving?", - Alignment::Center, + Title::from("There are unsaved changes! Save changes before leaving?") + .alignment(HorizontalAlignment::Center), ) .rewind(true) .choices(["Save", "Don't save", "Cancel"]), @@ -222,8 +240,8 @@ impl Default for QuitPopup { } } -impl Component for QuitPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for QuitPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Common(CommonMsg::CloseQuitPopup)) @@ -243,10 +261,10 @@ impl Component for QuitPopup { 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::Common(CommonMsg::SaveAndQuit)) } - CmdResult::Submit(State::One(StateValue::Usize(1))) => { + CmdResult::Submit(State::Single(StateValue::Usize(1))) => { Some(Msg::Common(CommonMsg::Quit)) } _ => Some(Msg::Common(CommonMsg::CloseQuitPopup)), @@ -256,7 +274,7 @@ impl Component for QuitPopup { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct SavePopup { component: Radio, } @@ -265,21 +283,25 @@ impl Default for SavePopup { fn default() -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::Yellow) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::Yellow) .modifiers(BorderType::Rounded), ) - .foreground(Color::Yellow) - .title("Save changes?", Alignment::Center) + .title(Title::from("Save changes?").alignment(HorizontalAlignment::Center)) .rewind(true) .choices(["Yes", "No"]), } } } -impl Component for SavePopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SavePopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Common(CommonMsg::CloseSavePopup)) @@ -309,7 +331,7 @@ impl Component for SavePopup { }) => { if matches!( self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) + CmdResult::Submit(State::Single(StateValue::Usize(0))) ) { Some(Msg::Common(CommonMsg::SaveConfig)) } else { diff --git a/src/ui/activities/setup/components/config.rs b/src/ui/activities/setup/components/config.rs index 5aee069..31d3aa9 100644 --- a/src/ui/activities/setup/components/config.rs +++ b/src/ui/activities/setup/components/config.rs @@ -2,11 +2,13 @@ //! //! config tab components -use tui_realm_stdlib::{Input, Radio}; +use tui_realm_stdlib::components::{Input, Radio}; use tuirealm::command::{Cmd, Direction, Position}; -use tuirealm::event::{Key, KeyEvent, KeyModifiers}; -use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style}; -use tuirealm::{Component, Event, MockComponent, NoUserEvent}; +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 super::{ConfigMsg, Msg}; use crate::explorer::GroupDirs as GroupDirsEnum; @@ -19,7 +21,7 @@ use crate::utils::parser::parse_bytesize; // -- components -#[derive(MockComponent)] +#[derive(Component)] pub struct CheckUpdates { component: Radio, } @@ -28,22 +30,26 @@ impl CheckUpdates { pub fn new(enabled: bool) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::LightYellow) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::LightYellow) .modifiers(BorderType::Rounded), ) .choices(["Yes", "No"]) - .foreground(Color::LightYellow) .rewind(true) - .title("Check for updates?", Alignment::Left) + .title(Title::from("Check for updates?").alignment(HorizontalAlignment::Left)) .value(usize::from(!enabled)), } } } -impl Component for CheckUpdates { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for CheckUpdates { + fn on(&mut self, ev: &Event) -> Option { handle_radio_ev( self, ev, @@ -53,7 +59,7 @@ impl Component for CheckUpdates { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct DefaultProtocol { component: Radio, } @@ -70,7 +76,7 @@ impl DefaultProtocol { .choices(["SFTP", "SCP", "FTP", "FTPS", "Kube", "S3", "SMB", "WebDAV"]) .foreground(Color::Cyan) .rewind(true) - .title("Default protocol", Alignment::Left) + .title(Title::from("Default protocol").alignment(HorizontalAlignment::Left)) .value(match protocol { FileTransferProtocol::Sftp => RADIO_PROTOCOL_SFTP, FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP, @@ -85,8 +91,8 @@ impl DefaultProtocol { } } -impl Component for DefaultProtocol { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for DefaultProtocol { + fn on(&mut self, ev: &Event) -> Option { handle_radio_ev( self, ev, @@ -96,7 +102,7 @@ impl Component for DefaultProtocol { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct GroupDirs { component: Radio, } @@ -105,15 +111,19 @@ impl GroupDirs { pub fn new(opt: Option) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::LightMagenta) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::LightMagenta) .modifiers(BorderType::Rounded), ) .choices(["Display first", "Display last", "No"]) - .foreground(Color::LightMagenta) .rewind(true) - .title("Group directories", Alignment::Left) + .title(Title::from("Group directories").alignment(HorizontalAlignment::Left)) .value(match opt { Some(GroupDirsEnum::First) => 0, Some(GroupDirsEnum::Last) => 1, @@ -123,8 +133,8 @@ impl GroupDirs { } } -impl Component for GroupDirs { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for GroupDirs { + fn on(&mut self, ev: &Event) -> Option { handle_radio_ev( self, ev, @@ -134,7 +144,7 @@ impl Component for GroupDirs { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct HiddenFiles { component: Radio, } @@ -151,14 +161,17 @@ impl HiddenFiles { .choices(["Yes", "No"]) .foreground(Color::LightRed) .rewind(true) - .title("Show hidden files? (by default)", Alignment::Left) + .title( + Title::from("Show hidden files? (by default)") + .alignment(HorizontalAlignment::Left), + ) .value(usize::from(!enabled)), } } } -impl Component for HiddenFiles { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for HiddenFiles { + fn on(&mut self, ev: &Event) -> Option { handle_radio_ev( self, ev, @@ -168,7 +181,7 @@ impl Component for HiddenFiles { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct NotificationsEnabled { component: Radio, } @@ -177,22 +190,26 @@ impl NotificationsEnabled { pub fn new(enabled: bool) -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::LightRed) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::LightRed) .modifiers(BorderType::Rounded), ) .choices(["Yes", "No"]) - .foreground(Color::LightRed) .rewind(true) - .title("Enable notifications?", Alignment::Left) + .title(Title::from("Enable notifications?").alignment(HorizontalAlignment::Left)) .value(usize::from(!enabled)), } } } -impl Component for NotificationsEnabled { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for NotificationsEnabled { + fn on(&mut self, ev: &Event) -> Option { handle_radio_ev( self, ev, @@ -202,7 +219,7 @@ impl Component for NotificationsEnabled { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct PromptOnFileReplace { component: Radio, } @@ -219,14 +236,17 @@ impl PromptOnFileReplace { .choices(["Yes", "No"]) .foreground(Color::LightBlue) .rewind(true) - .title("Prompt when replacing existing files?", Alignment::Left) + .title( + Title::from("Prompt when replacing existing files?") + .alignment(HorizontalAlignment::Left), + ) .value(usize::from(!enabled)), } } } -impl Component for PromptOnFileReplace { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for PromptOnFileReplace { + fn on(&mut self, ev: &Event) -> Option { handle_radio_ev( self, ev, @@ -236,7 +256,7 @@ impl Component for PromptOnFileReplace { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct LocalFileFmt { component: Input, } @@ -252,18 +272,21 @@ impl LocalFileFmt { ) .foreground(Color::LightGreen) .input_type(InputType::Text) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "{NAME:36} {PEX} {SIZE} {MTIME:17:%b %d %Y %H:%M}", Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("File formatter syntax (local)") + .alignment(HorizontalAlignment::Left), ) - .title("File formatter syntax (local)", Alignment::Left) .value(value), } } } -impl Component for LocalFileFmt { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for LocalFileFmt { + fn on(&mut self, ev: &Event) -> Option { handle_input_ev( self, ev, @@ -273,7 +296,7 @@ impl Component for LocalFileFmt { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct NotificationsThreshold { component: Input, } @@ -297,15 +320,21 @@ impl NotificationsThreshold { .foreground(Color::LightYellow) .invalid_style(Style::default().fg(Color::Red)) .input_type(InputType::Custom(validate, char_valid)) - .placeholder("64 MB", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Notifications: minimum transfer size", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "64 MB", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("Notifications: minimum transfer size") + .alignment(HorizontalAlignment::Left), + ) .value(value), } } } -impl Component for NotificationsThreshold { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for NotificationsThreshold { + fn on(&mut self, ev: &Event) -> Option { handle_input_ev( self, ev, @@ -315,7 +344,7 @@ impl Component for NotificationsThreshold { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct RemoteFileFmt { component: Input, } @@ -331,18 +360,21 @@ impl RemoteFileFmt { ) .foreground(Color::Cyan) .input_type(InputType::Text) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "{NAME:36} {PEX} {SIZE} {MTIME:17:%b %d %Y %H:%M}", Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title( + Title::from("File formatter syntax (remote)") + .alignment(HorizontalAlignment::Left), ) - .title("File formatter syntax (remote)", Alignment::Left) .value(value), } } } -impl Component for RemoteFileFmt { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for RemoteFileFmt { + fn on(&mut self, ev: &Event) -> Option { handle_input_ev( self, ev, @@ -352,7 +384,7 @@ impl Component for RemoteFileFmt { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct SshConfig { component: Input, } @@ -368,18 +400,18 @@ impl SshConfig { ) .foreground(Color::LightBlue) .input_type(InputType::Text) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "~/.ssh/config", Style::default().fg(Color::Rgb(128, 128, 128)), - ) - .title("SSH configuration path", Alignment::Left) + )) + .title(Title::from("SSH configuration path").alignment(HorizontalAlignment::Left)) .value(value), } } } -impl Component for SshConfig { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SshConfig { + fn on(&mut self, ev: &Event) -> Option { handle_input_ev( self, ev, @@ -389,7 +421,7 @@ impl Component for SshConfig { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct TextEditor { component: Input, } @@ -405,15 +437,18 @@ impl TextEditor { ) .foreground(Color::LightGreen) .input_type(InputType::Text) - .placeholder("vim", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Text editor", Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "vim", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Text editor").alignment(HorizontalAlignment::Left)) .value(value), } } } -impl Component for TextEditor { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for TextEditor { + fn on(&mut self, ev: &Event) -> Option { handle_input_ev( self, ev, @@ -426,8 +461,8 @@ impl Component for TextEditor { // -- event handler fn handle_input_ev( - component: &mut dyn Component, - ev: Event, + component: &mut dyn AppComponent, + ev: &Event, on_key_down: Msg, on_key_up: Msg, ) -> Option { @@ -476,7 +511,7 @@ fn handle_input_ev( code: Key::Char(ch), .. }) => { - component.perform(Cmd::Type(ch)); + component.perform(Cmd::Type(*ch)); Some(Msg::Config(ConfigMsg::ConfigChanged)) } Event::Keyboard(KeyEvent { @@ -488,8 +523,8 @@ fn handle_input_ev( } fn handle_radio_ev( - component: &mut dyn Component, - ev: Event, + component: &mut dyn AppComponent, + ev: &Event, on_key_down: Msg, on_key_up: Msg, ) -> Option { diff --git a/src/ui/activities/setup/components/ssh.rs b/src/ui/activities/setup/components/ssh.rs index c0bafa2..7d19592 100644 --- a/src/ui/activities/setup/components/ssh.rs +++ b/src/ui/activities/setup/components/ssh.rs @@ -2,13 +2,15 @@ //! //! ssh components -use tui_realm_stdlib::{Input, List, Radio}; +use tui_realm_stdlib::components::{Input, List, Radio}; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; -use tuirealm::event::{Key, KeyEvent, KeyModifiers}; +use tuirealm::component::{AppComponent, Component}; +use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent}; use tuirealm::props::{ - Alignment, BorderSides, BorderType, Borders, Color, InputType, Style, TextSpan, + BorderSides, BorderType, Borders, Color, HorizontalAlignment, InputType, SpanStatic, Style, + TextModifiers, Title, }; -use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue}; +use tuirealm::state::{State, StateValue}; use super::{Msg, SshMsg}; @@ -17,7 +19,7 @@ SshHost, SshKeys, SshUsername, */ -#[derive(MockComponent)] +#[derive(Component)] pub struct DelSshKeyPopup { component: Radio, } @@ -26,22 +28,26 @@ impl Default for DelSshKeyPopup { fn default() -> Self { Self { component: Radio::default() + .highlight_style( + Style::default() + .fg(Color::Red) + .add_modifier(TextModifiers::REVERSED), + ) .borders( Borders::default() .color(Color::Red) .modifiers(BorderType::Rounded), ) .choices(["Yes", "No"]) - .foreground(Color::Red) .rewind(true) - .title("Delete key?", Alignment::Center) + .title(Title::from("Delete key?").alignment(HorizontalAlignment::Center)) .value(1), } } } -impl Component for DelSshKeyPopup { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for DelSshKeyPopup { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { Some(Msg::Ssh(SshMsg::CloseDelSshKeyPopup)) @@ -71,7 +77,7 @@ impl Component for DelSshKeyPopup { }) => { if matches!( self.perform(Cmd::Submit), - CmdResult::Submit(State::One(StateValue::Usize(0))) + CmdResult::Submit(State::Single(StateValue::Usize(0))) ) { Some(Msg::Ssh(SshMsg::DeleteSshKey)) } else { @@ -83,7 +89,7 @@ impl Component for DelSshKeyPopup { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct SshKeys { component: List, } @@ -98,18 +104,22 @@ impl SshKeys { .modifiers(BorderType::Rounded), ) .foreground(Color::LightGreen) - .highlighted_color(Color::LightGreen) + .highlight_style(Style::default().fg(Color::LightGreen)) .rewind(true) - .rows(keys.iter().map(|x| vec![TextSpan::from(x)]).collect()) + .rows( + keys.iter() + .map(|x| vec![SpanStatic::from(x.clone())]) + .collect::>>(), + ) .step(4) .scroll(true) - .title("SSH Keys", Alignment::Left), + .title(Title::from("SSH Keys").alignment(HorizontalAlignment::Left)), } } } -impl Component for SshKeys { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SshKeys { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Down, .. @@ -147,7 +157,9 @@ impl Component for SshKeys { Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => match self.state() { - State::One(StateValue::Usize(choice)) => Some(Msg::Ssh(SshMsg::EditSshKey(choice))), + State::Single(StateValue::Usize(choice)) => { + Some(Msg::Ssh(SshMsg::EditSshKey(choice))) + } _ => Some(Msg::None), }, Event::Keyboard(KeyEvent { @@ -163,7 +175,7 @@ impl Component for SshKeys { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct SshHost { component: Input, } @@ -177,17 +189,17 @@ impl Default for SshHost { .sides(BorderSides::TOP | BorderSides::RIGHT | BorderSides::LEFT), ) .input_type(InputType::Text) - .placeholder( + .placeholder(tuirealm::props::SpanStatic::styled( "192.168.1.2", Style::default().fg(Color::Rgb(128, 128, 128)), - ) - .title("Hostname or address", Alignment::Center), + )) + .title(Title::from("Hostname or address").alignment(HorizontalAlignment::Center)), } } } -impl Component for SshHost { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SshHost { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -233,7 +245,7 @@ impl Component for SshHost { code: Key::Char(ch), .. }) => { - self.perform(Cmd::Type(ch)); + self.perform(Cmd::Type(*ch)); Some(Msg::None) } Event::Keyboard(KeyEvent { @@ -250,7 +262,7 @@ impl Component for SshHost { } } -#[derive(MockComponent)] +#[derive(Component)] pub struct SshUsername { component: Input, } @@ -264,14 +276,17 @@ impl Default for SshUsername { .sides(BorderSides::BOTTOM | BorderSides::RIGHT | BorderSides::LEFT), ) .input_type(InputType::Text) - .placeholder("root", Style::default().fg(Color::Rgb(128, 128, 128))) - .title("Username", Alignment::Center), + .placeholder(tuirealm::props::SpanStatic::styled( + "root", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from("Username").alignment(HorizontalAlignment::Center)), } } } -impl Component for SshUsername { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for SshUsername { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -312,7 +327,7 @@ impl Component for SshUsername { code: Key::Char(ch), modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT, }) => { - self.perform(Cmd::Type(ch)); + self.perform(Cmd::Type(*ch)); Some(Msg::None) } Event::Keyboard(KeyEvent { diff --git a/src/ui/activities/setup/components/theme.rs b/src/ui/activities/setup/components/theme.rs index 6f2c344..ef98669 100644 --- a/src/ui/activities/setup/components/theme.rs +++ b/src/ui/activities/setup/components/theme.rs @@ -2,20 +2,22 @@ //! //! theme tab components -use tui_realm_stdlib::{Input, Label}; +use tui_realm_stdlib::components::{Input, Label}; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; -use tuirealm::event::{Key, KeyEvent, KeyModifiers}; -use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style, TextModifiers}; -use tuirealm::{ - AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent, State, StateValue, +use tuirealm::component::{AppComponent, Component}; +use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent}; +use tuirealm::props::{ + AttrValue, Attribute, BorderType, Borders, Color, HorizontalAlignment, InputType, Style, + TextModifiers, Title, }; +use tuirealm::state::{State, StateValue}; use super::{Msg, ThemeMsg}; use crate::ui::activities::setup::IdTheme; // -- components -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthTitle { component: Label, } @@ -30,13 +32,13 @@ impl Default for AuthTitle { } } -impl Component for AuthTitle { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for AuthTitle { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscTitle { component: Label, } @@ -51,13 +53,13 @@ impl Default for MiscTitle { } } -impl Component for MiscTitle { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for MiscTitle { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct TransferTitle { component: Label, } @@ -72,13 +74,13 @@ impl Default for TransferTitle { } } -impl Component for TransferTitle { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for TransferTitle { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct TransferTitle2 { component: Label, } @@ -93,13 +95,13 @@ impl Default for TransferTitle2 { } } -impl Component for TransferTitle2 { - fn on(&mut self, _ev: Event) -> Option { +impl AppComponent for TransferTitle2 { + fn on(&mut self, _ev: &Event) -> Option { None } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthAddress { component: InputColor, } @@ -118,13 +120,13 @@ impl AuthAddress { } } -impl Component for AuthAddress { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthAddress { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthBookmarks { component: InputColor, } @@ -143,13 +145,13 @@ impl AuthBookmarks { } } -impl Component for AuthBookmarks { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthBookmarks { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthPassword { component: InputColor, } @@ -168,13 +170,13 @@ impl AuthPassword { } } -impl Component for AuthPassword { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthPassword { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthPort { component: InputColor, } @@ -193,13 +195,13 @@ impl AuthPort { } } -impl Component for AuthPort { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthPort { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthProtocol { component: InputColor, } @@ -218,13 +220,13 @@ impl AuthProtocol { } } -impl Component for AuthProtocol { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthProtocol { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthRecentHosts { component: InputColor, } @@ -243,12 +245,12 @@ impl AuthRecentHosts { } } -impl Component for AuthRecentHosts { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthRecentHosts { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct AuthUsername { component: InputColor, } @@ -267,13 +269,13 @@ impl AuthUsername { } } -impl Component for AuthUsername { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for AuthUsername { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ExplorerLocalBg { component: InputColor, } @@ -292,13 +294,13 @@ impl ExplorerLocalBg { } } -impl Component for ExplorerLocalBg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerLocalBg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ExplorerLocalFg { component: InputColor, } @@ -317,13 +319,13 @@ impl ExplorerLocalFg { } } -impl Component for ExplorerLocalFg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerLocalFg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ExplorerLocalHg { component: InputColor, } @@ -342,13 +344,13 @@ impl ExplorerLocalHg { } } -impl Component for ExplorerLocalHg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerLocalHg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ExplorerRemoteBg { component: InputColor, } @@ -367,13 +369,13 @@ impl ExplorerRemoteBg { } } -impl Component for ExplorerRemoteBg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerRemoteBg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ExplorerRemoteFg { component: InputColor, } @@ -392,13 +394,13 @@ impl ExplorerRemoteFg { } } -impl Component for ExplorerRemoteFg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerRemoteFg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ExplorerRemoteHg { component: InputColor, } @@ -417,13 +419,13 @@ impl ExplorerRemoteHg { } } -impl Component for ExplorerRemoteHg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ExplorerRemoteHg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct LogBg { component: InputColor, } @@ -442,13 +444,13 @@ impl LogBg { } } -impl Component for LogBg { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for LogBg { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct LogWindow { component: InputColor, } @@ -467,13 +469,13 @@ impl LogWindow { } } -impl Component for LogWindow { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for LogWindow { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscError { component: InputColor, } @@ -492,13 +494,13 @@ impl MiscError { } } -impl Component for MiscError { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscError { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscInfo { component: InputColor, } @@ -517,13 +519,13 @@ impl MiscInfo { } } -impl Component for MiscInfo { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscInfo { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscInput { component: InputColor, } @@ -542,13 +544,13 @@ impl MiscInput { } } -impl Component for MiscInput { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscInput { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscKeys { component: InputColor, } @@ -567,13 +569,13 @@ impl MiscKeys { } } -impl Component for MiscKeys { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscKeys { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscQuit { component: InputColor, } @@ -592,13 +594,13 @@ impl MiscQuit { } } -impl Component for MiscQuit { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscQuit { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscSave { component: InputColor, } @@ -617,13 +619,13 @@ impl MiscSave { } } -impl Component for MiscSave { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscSave { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct MiscWarn { component: InputColor, } @@ -642,13 +644,13 @@ impl MiscWarn { } } -impl Component for MiscWarn { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for MiscWarn { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct ProgBar { component: InputColor, } @@ -667,13 +669,13 @@ impl ProgBar { } } -impl Component for ProgBar { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for ProgBar { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct StatusHidden { component: InputColor, } @@ -692,13 +694,13 @@ impl StatusHidden { } } -impl Component for StatusHidden { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for StatusHidden { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct StatusSorting { component: InputColor, } @@ -717,13 +719,13 @@ impl StatusSorting { } } -impl Component for StatusSorting { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for StatusSorting { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } -#[derive(MockComponent)] +#[derive(Component)] pub struct StatusSync { component: InputColor, } @@ -742,15 +744,15 @@ impl StatusSync { } } -impl Component for StatusSync { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for StatusSync { + fn on(&mut self, ev: &Event) -> Option { self.component.on(ev) } } // -- input color -#[derive(MockComponent)] +#[derive(Component)] struct InputColor { component: Input, id: IdTheme, @@ -770,8 +772,11 @@ impl InputColor { ) .foreground(color) .input_type(InputType::Color) - .placeholder("#aa33ee", Style::default().fg(Color::Rgb(128, 128, 128))) - .title(name, Alignment::Left) + .placeholder(tuirealm::props::SpanStatic::styled( + "#aa33ee", + Style::default().fg(Color::Rgb(128, 128, 128)), + )) + .title(Title::from(name.to_string()).alignment(HorizontalAlignment::Left)) .value(value), id, on_key_down, @@ -780,7 +785,7 @@ impl InputColor { } fn update_color(&mut self, result: CmdResult) -> Option { - if let CmdResult::Changed(State::One(StateValue::String(color))) = result { + if let CmdResult::Changed(State::Single(StateValue::String(color))) = result { let color = tuirealm::utils::parser::parse_color(&color).unwrap(); self.attr(Attribute::Foreground, AttrValue::Color(color)); self.attr( @@ -807,8 +812,8 @@ impl InputColor { } } -impl Component for InputColor { - fn on(&mut self, ev: Event) -> Option { +impl AppComponent for InputColor { + fn on(&mut self, ev: &Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. @@ -854,7 +859,7 @@ impl Component for InputColor { code: Key::Char(ch), .. }) => { - let result = self.perform(Cmd::Type(ch)); + let result = self.perform(Cmd::Type(*ch)); self.update_color(result) } Event::Keyboard(KeyEvent { diff --git a/src/ui/activities/setup/config.rs b/src/ui/activities/setup/config.rs index 60d66b1..5fa465c 100644 --- a/src/ui/activities/setup/config.rs +++ b/src/ui/activities/setup/config.rs @@ -7,6 +7,8 @@ // Ext use std::env; +use tuirealm::terminal::TerminalAdapter; + use super::SetupActivity; impl SetupActivity { diff --git a/src/ui/activities/setup/update.rs b/src/ui/activities/setup/update.rs index 24589ce..6ba2301 100644 --- a/src/ui/activities/setup/update.rs +++ b/src/ui/activities/setup/update.rs @@ -5,17 +5,16 @@ // locals // ext -use tuirealm::Update; use super::{ CommonMsg, ConfigMsg, Id, IdConfig, IdSsh, IdTheme, Msg, SetupActivity, SshMsg, ThemeMsg, ViewLayout, }; -impl Update for SetupActivity { +impl SetupActivity { /// Update auth activity model based on msg /// The function exits when returns None - fn update(&mut self, msg: Option) -> Option { + pub(super) fn update(&mut self, msg: Option) -> Option { match msg.unwrap_or(Msg::None) { Msg::Common(msg) => self.common_update(msg), Msg::Config(msg) => self.config_update(msg), diff --git a/src/ui/activities/setup/view.rs b/src/ui/activities/setup/view.rs index 3eb18cf..9ac7fb6 100644 --- a/src/ui/activities/setup/view.rs +++ b/src/ui/activities/setup/view.rs @@ -8,8 +8,9 @@ pub mod ssh_keys; pub mod theme; use tuirealm::event::{Key, KeyEvent, KeyModifiers}; +use tuirealm::ratatui::Frame; use tuirealm::ratatui::widgets::Clear; -use tuirealm::{Frame, Sub, SubClause, SubEventClause}; +use tuirealm::subscription::{EventClause, Sub, SubClause}; use super::*; use crate::utils::ui::{Popup, Size}; @@ -172,62 +173,62 @@ impl SetupActivity { Box::::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::Function(10), modifiers: KeyModifiers::NONE, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Tab, modifiers: KeyModifiers::NONE, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Char('h'), modifiers: KeyModifiers::CONTROL, }), 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::Char('r'), modifiers: KeyModifiers::CONTROL, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Char('s'), modifiers: KeyModifiers::CONTROL, }), Self::no_popup_mounted_clause(), ), Sub::new( - SubEventClause::Keyboard(KeyEvent { + EventClause::Keyboard(KeyEvent { code: Key::Function(4), modifiers: KeyModifiers::NONE, }), Self::no_popup_mounted_clause(), ), - Sub::new(SubEventClause::WindowResize, SubClause::Always), + Sub::new(EventClause::WindowResize, SubClause::Always), ], ) { error!("Failed to mount component: {err}"); diff --git a/src/ui/activities/setup/view/setup.rs b/src/ui/activities/setup/view/setup.rs index 97e7007..31c0b75 100644 --- a/src/ui/activities/setup/view/setup.rs +++ b/src/ui/activities/setup/view/setup.rs @@ -8,7 +8,8 @@ use std::path::PathBuf; use tuirealm::ratatui::layout::{Constraint, Direction, Layout}; -use tuirealm::{State, StateValue}; +use tuirealm::state::{State, StateValue}; +use tuirealm::terminal::TerminalAdapter; use super::{ Context, Id, IdCommon, IdConfig, RADIO_PROTOCOL_KUBE, RADIO_PROTOCOL_WEBDAV, SetupActivity, @@ -256,13 +257,13 @@ impl SetupActivity { /// Collect values from input and put them into the configuration pub(crate) fn collect_input_values(&mut self) { - if let Ok(State::One(StateValue::String(editor))) = + if let Ok(State::Single(StateValue::String(editor))) = self.app.state(&Id::Config(IdConfig::TextEditor)) { self.config_mut() .set_text_editor(PathBuf::from(editor.as_str())); } - if let Ok(State::One(StateValue::Usize(protocol))) = + if let Ok(State::Single(StateValue::Usize(protocol))) = self.app.state(&Id::Config(IdConfig::DefaultProtocol)) { let protocol: FileTransferProtocol = match protocol { @@ -277,35 +278,35 @@ impl SetupActivity { }; self.config_mut().set_default_protocol(protocol); } - if let Ok(State::One(StateValue::Usize(opt))) = + if let Ok(State::Single(StateValue::Usize(opt))) = self.app.state(&Id::Config(IdConfig::HiddenFiles)) { let show: bool = matches!(opt, 0); self.config_mut().set_show_hidden_files(show); } - if let Ok(State::One(StateValue::Usize(opt))) = + if let Ok(State::Single(StateValue::Usize(opt))) = self.app.state(&Id::Config(IdConfig::CheckUpdates)) { let check: bool = matches!(opt, 0); self.config_mut().set_check_for_updates(check); } - if let Ok(State::One(StateValue::Usize(opt))) = + if let Ok(State::Single(StateValue::Usize(opt))) = self.app.state(&Id::Config(IdConfig::PromptOnFileReplace)) { let check: bool = matches!(opt, 0); self.config_mut().set_prompt_on_file_replace(check); } - if let Ok(State::One(StateValue::String(fmt))) = + if let Ok(State::Single(StateValue::String(fmt))) = self.app.state(&Id::Config(IdConfig::LocalFileFmt)) { self.config_mut().set_local_file_fmt(fmt); } - if let Ok(State::One(StateValue::String(fmt))) = + if let Ok(State::Single(StateValue::String(fmt))) = self.app.state(&Id::Config(IdConfig::RemoteFileFmt)) { self.config_mut().set_remote_file_fmt(fmt); } - if let Ok(State::One(StateValue::Usize(opt))) = + if let Ok(State::Single(StateValue::Usize(opt))) = self.app.state(&Id::Config(IdConfig::GroupDirs)) { let dirs: Option = match opt { @@ -315,18 +316,18 @@ impl SetupActivity { }; self.config_mut().set_group_dirs(dirs); } - if let Ok(State::One(StateValue::Usize(opt))) = + if let Ok(State::Single(StateValue::Usize(opt))) = self.app.state(&Id::Config(IdConfig::NotificationsEnabled)) { self.config_mut().set_notifications(opt == 0); } - if let Ok(State::One(StateValue::U64(bytes))) = self + if let Ok(State::Single(StateValue::U64(bytes))) = self .app .state(&Id::Config(IdConfig::NotificationsThreshold)) { self.config_mut().set_notification_threshold(bytes); } - if let Ok(State::One(StateValue::String(mut path))) = + if let Ok(State::Single(StateValue::String(mut path))) = self.app.state(&Id::Config(IdConfig::SshConfig)) { if path.is_empty() { diff --git a/src/ui/activities/setup/view/ssh_keys.rs b/src/ui/activities/setup/view/ssh_keys.rs index 61d5a57..ba25eb9 100644 --- a/src/ui/activities/setup/view/ssh_keys.rs +++ b/src/ui/activities/setup/view/ssh_keys.rs @@ -7,6 +7,7 @@ // Ext use tuirealm::ratatui::layout::{Constraint, Direction, Layout}; use tuirealm::ratatui::widgets::Clear; +use tuirealm::terminal::TerminalAdapter; use super::{Context, Id, IdCommon, IdSsh, SetupActivity, ViewLayout, components}; use crate::utils::ui::{Popup, Size}; diff --git a/src/ui/activities/setup/view/theme.rs b/src/ui/activities/setup/view/theme.rs index 8d9c661..8f49cfd 100644 --- a/src/ui/activities/setup/view/theme.rs +++ b/src/ui/activities/setup/view/theme.rs @@ -6,6 +6,7 @@ // Locals // Ext use tuirealm::ratatui::layout::{Constraint, Direction, Layout}; +use tuirealm::terminal::TerminalAdapter; use super::{Context, Id, IdCommon, IdTheme, SetupActivity, Theme, ViewLayout, components}; diff --git a/src/ui/context.rs b/src/ui/context.rs index baecbf6..943b521 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -3,7 +3,7 @@ //! `Context` is the module which provides all the functionalities related to the UI data holder, called Context // Locals -use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalBridge}; +use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalAdapter}; use super::store::Store; use crate::filetransfer::{FileTransferParams, HostBridgeParams}; @@ -18,7 +18,7 @@ pub struct Context { bookmarks_client: Option, config_client: ConfigClient, pub(crate) store: Store, - pub(crate) terminal: TerminalBridge, + pub(crate) terminal: CrosstermTerminalAdapter, theme_provider: ThemeProvider, error: Option, } @@ -31,7 +31,13 @@ impl Context { theme_provider: ThemeProvider, error: Option, ) -> Context { - let mut terminal = TerminalBridge::init_crossterm().expect("Could not initialize terminal"); + let mut terminal = CrosstermTerminalAdapter::new().expect("Could not initialize terminal"); + terminal + .enable_raw_mode() + .expect("Could not enable terminal raw mode"); + terminal + .enter_alternate_screen() + .expect("Could not enter alternate screen"); let _ = terminal.disable_mouse_capture(); Context { @@ -88,7 +94,7 @@ impl Context { &mut self.theme_provider } - pub fn terminal(&mut self) -> &mut TerminalBridge { + pub fn terminal(&mut self) -> &mut CrosstermTerminalAdapter { &mut self.terminal } diff --git a/src/utils/tty.rs b/src/utils/tty.rs index d6a03de..9110784 100644 --- a/src/utils/tty.rs +++ b/src/utils/tty.rs @@ -2,26 +2,26 @@ //! //! `Utils` implements utilities functions to work with layouts -use tuirealm::terminal::{TerminalAdapter, TerminalBridge}; +use tuirealm::terminal::TerminalAdapter; /// Read a secret from tty with customisable prompt pub fn read_secret_from_tty( - terminal_bridge: &mut TerminalBridge, + terminal: &mut T, prompt: impl ToString, ) -> std::io::Result> where T: TerminalAdapter, { - let _ = terminal_bridge.disable_raw_mode(); - let _ = terminal_bridge.leave_alternate_screen(); + let _ = terminal.disable_raw_mode(); + let _ = terminal.leave_alternate_screen(); let res = match rpassword::prompt_password(prompt) { Ok(p) if p.is_empty() => Ok(None), Ok(p) => Ok(Some(p)), Err(err) => Err(err), }; - let _ = terminal_bridge.enter_alternate_screen(); - let _ = terminal_bridge.enable_raw_mode(); + let _ = terminal.enter_alternate_screen(); + let _ = terminal.enable_raw_mode(); res }