diff --git a/Cargo.lock b/Cargo.lock index 17452c8e..71849401 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ dependencies = [ "mach 0.1.2", ] +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + [[package]] name = "ab_glyph" version = "0.2.6" @@ -80,6 +86,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "approx" version = "0.4.0" @@ -1191,6 +1203,7 @@ dependencies = [ "iced_futures", "iced_graphics", "iced_native", + "iced_pure", "iced_wgpu", "iced_winit", "thiserror", @@ -1228,6 +1241,7 @@ dependencies = [ "bytemuck", "glam", "iced_native", + "iced_pure", "iced_style", "lyon", "qrcode", @@ -1235,6 +1249,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "iced_lazy" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4518be1d17a50904242f848f379357af899232e9f43b0b4ebeab9405518841" +dependencies = [ + "iced_native", + "iced_pure", + "ouroboros", +] + [[package]] name = "iced_native" version = "0.5.0" @@ -1249,6 +1274,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "iced_pure" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80aeaecadfd6832c2c787cbdfd357adc256a51c55d68142d852037451e72f393" +dependencies = [ + "iced_native", + "iced_style", + "num-traits", +] + [[package]] name = "iced_style" version = "0.4.0" @@ -1944,6 +1980,30 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ouroboros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f357ef82d1b4db66fbed0b8d542cbd3c22d0bf5b393b3c257b9ba4568e70c9c3" +dependencies = [ + "aliasable", + "ouroboros_macro", + "stable_deref_trait", +] + +[[package]] +name = "ouroboros_macro" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44a0b52c2cbaef7dffa5fec1a43274afe8bd2a644fa9fc50a9ef4ff0269b1257" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "owned_ttf_parser" version = "0.8.0" @@ -2057,6 +2117,30 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.36" @@ -2375,6 +2459,9 @@ name = "revault_ui" version = "0.1.0" dependencies = [ "iced", + "iced_lazy", + "iced_native", + "iced_pure", ] [[package]] @@ -2723,6 +2810,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 00a70f5d..693b7705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ bitcoin = { version = "0.27", features = ["base64", "use-serde"] } revaultd = { version = "0.4.0", default-features = false} backtrace = "0.3" -iced = { version = "0.4", default-features= false, features = ["tokio", "wgpu", "svg", "qr_code"] } +iced = { version = "0.4", default-features= false, features = ["tokio", "wgpu", "svg", "qr_code", "pure"] } iced_native = "0.5" revault_ui = { path = "./ui" } revault_hwi = { path = "./hwi" } diff --git a/src/app/view/vault.rs b/src/app/view/vault.rs index 4de0286e..7637d573 100644 --- a/src/app/view/vault.rs +++ b/src/app/view/vault.rs @@ -1,10 +1,10 @@ use chrono::NaiveDateTime; -use iced::{tooltip, Alignment, Column, Container, Element, Length, Row, Tooltip}; +use iced::{pure::Pure, tooltip, Alignment, Column, Container, Element, Length, Row, Tooltip}; use bitcoin::{util::bip32::Fingerprint, Amount}; use revault_ui::{ color, - component::{badge, button, card, separation, text::Text, TooltipStyle}, + component::{badge, button, card, collapse::collapse, separation, text::Text, TooltipStyle}, icon, }; @@ -14,10 +14,16 @@ use crate::daemon::model::{ outpoint, transaction_from_hex, Vault, VaultStatus, VaultTransactions, WalletTransaction, }; -#[derive(Debug)] pub struct VaultModal { copy_button: iced::button::State, modal: layout::Modal, + state: iced::pure::State, +} + +impl std::fmt::Debug for VaultModal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VaultModal").finish() + } } impl VaultModal { @@ -25,6 +31,7 @@ impl VaultModal { VaultModal { copy_button: iced::button::State::default(), modal: layout::Modal::default(), + state: iced::pure::State::default(), } } @@ -51,7 +58,12 @@ impl VaultModal { if let Some(tx) = &txs.unvault { col = col.push(transaction(ctx, "Unvault transaction", tx)); } - col = col.push(transaction(ctx, "Deposit transaction", &txs.deposit)); + col = col.push(transaction_collapse( + ctx, + "Deposit transaction", + &txs.deposit, + &mut self.state, + )); self.modal.view( ctx, @@ -120,6 +132,33 @@ fn vault<'a>( )) } +fn transaction_collapse<'a, T: Clone + 'a>( + ctx: &Context, + title: &str, + transaction: &WalletTransaction, + state: &'a mut iced::pure::State, +) -> Container<'a, T> { + let tx = transaction_from_hex(&transaction.hex); + Container::new(Pure::new( + state, + collapse::<_, T, _, _, _>( + move || { + iced::pure::row() + .push(iced::pure::text("hello")) + .push(iced::pure::text("hello-again")) + .width(Length::Fill) + .spacing(20) + .into() + }, + move || { + iced::pure::column() + .push(iced::pure::text(format!("{}", tx.txid()))) + .into() + }, + ), + )) +} + fn transaction<'a, T: 'a>( ctx: &Context, title: &str, diff --git a/tests/utils/sandbox.rs b/tests/utils/sandbox.rs index 000631aa..758e1ac7 100644 --- a/tests/utils/sandbox.rs +++ b/tests/utils/sandbox.rs @@ -5,7 +5,7 @@ pub struct Sandbox { state: S, } -impl Sandbox { +impl Sandbox { pub fn new(state: S) -> Self { return Self { state }; } diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 8f8766b3..a651092d 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -9,4 +9,7 @@ edition = "2018" resolver = "2" [dependencies] -iced = { version = "0.4", default-features= false, features = ["wgpu", "svg"] } +iced = { version = "0.4", default-features= false, features = ["wgpu", "svg", "pure"] } +iced_lazy = { version = "0.1", features = ["pure"]} +iced_native = "0.5" +iced_pure = "0.2.2" diff --git a/ui/src/component/collapse.rs b/ui/src/component/collapse.rs new file mode 100644 index 00000000..5f4855a3 --- /dev/null +++ b/ui/src/component/collapse.rs @@ -0,0 +1,87 @@ +use iced::{ + alignment, + pure::{button, column, row, text}, + Alignment, Length, +}; +use iced_lazy::pure::{self, Component}; +use iced_native::text; +use iced_pure::Element; +use std::marker::PhantomData; + +pub fn collapse< + 'a, + Message: 'a, + T: Into + Clone + 'a, + Renderer: text::Renderer + 'static, + H: Fn() -> Element<'a, T, Renderer> + 'a, + C: Fn() -> Element<'a, T, Renderer> + 'a, +>( + header: H, + content: C, +) -> impl Into> { + Collapse { + header, + content, + phantom: PhantomData, + } +} + +struct Collapse<'a, H, C> { + header: H, + content: C, + phantom: PhantomData<&'a H>, +} + +#[derive(Debug, Clone, Copy)] +enum Event { + Internal(T), + Collapse(bool), +} + +impl<'a, Message, Renderer, T, H, C> Component for Collapse<'a, H, C> +where + T: Into + Clone + 'a, + H: Fn() -> Element<'a, T, Renderer>, + C: Fn() -> Element<'a, T, Renderer>, + Renderer: text::Renderer + 'static, +{ + type State = bool; + type Event = Event; + + fn update(&mut self, state: &mut Self::State, event: Event) -> Option { + match event { + Event::Internal(e) => Some(e.into()), + Event::Collapse(s) => { + *state = s; + None + } + } + } + + fn view(&self, state: &Self::State) -> Element { + if *state { + column() + .push(button((self.header)().map(Event::Internal)).on_press(Event::Collapse(false))) + .push((self.content)().map(Event::Internal)) + .into() + } else { + column() + .push(button((self.header)().map(Event::Internal)).on_press(Event::Collapse(true))) + .into() + } + } +} + +impl<'a, Message, Renderer, T, H: 'a, C: 'a> From> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: 'static + text::Renderer, + T: Into + Clone + 'a, + H: Fn() -> Element<'a, T, Renderer>, + C: Fn() -> Element<'a, T, Renderer>, +{ + fn from(c: Collapse<'a, H, C>) -> Self { + pure::component(c).into() + } +} diff --git a/ui/src/component/mod.rs b/ui/src/component/mod.rs index 1dfacf21..fb1cf353 100644 --- a/ui/src/component/mod.rs +++ b/ui/src/component/mod.rs @@ -1,5 +1,6 @@ pub mod badge; pub mod button; +pub mod collapse; pub mod form; pub mod image; pub mod notification;