Bump iced 0.5

This commit is contained in:
edouard 2022-11-23 20:35:30 +01:00
parent 60defd672c
commit de738105c0
35 changed files with 1496 additions and 1132 deletions

873
gui/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,9 @@ liana = { git = "https://github.com/revault/liana", branch = "master", default-f
backtrace = "0.3"
base64 = "0.13"
iced = { version = "0.4", default-features= false, features = ["tokio", "wgpu", "svg", "qr_code", "pure"] }
iced_native = "0.5"
iced_lazy = { version = "0.1.1", features = ["pure"] }
iced_pure = "0.2.2"
iced = { version = "0.5", default-features= false, features = ["tokio", "wgpu", "svg", "qr_code"] }
iced_native = "0.6"
iced_lazy = { version = "0.2"}
tokio = {version = "1.21.0", features = ["signal"]}
serde = { version = "1.0", features = ["derive"] }

View File

@ -12,8 +12,7 @@ use std::io::Write;
use std::sync::Arc;
use std::time::Duration;
use iced::pure::Element;
use iced::{clipboard, time, Command, Subscription};
use iced::{clipboard, time, Command, Element, Subscription};
use iced_native::{window, Event};
pub use liana::config::Config as DaemonConfig;

View File

@ -1,7 +1,6 @@
use std::sync::Arc;
use iced::pure::Element;
use iced::Command;
use iced::{Command, Element};
use crate::{
app::{cache::Cache, error::Error, menu::Menu, message::Message, state::State, view},
@ -10,7 +9,7 @@ use crate::{
pub struct CoinsPanel {
coins: Vec<Coin>,
selected_coin: Option<usize>,
selected: Vec<usize>,
warning: Option<Error>,
/// timelock value to pass for the heir to consume a coin.
timelock: u32,
@ -29,7 +28,7 @@ impl CoinsPanel {
}
})
.collect(),
selected_coin: None,
selected: Vec::new(),
warning: None,
timelock,
}
@ -42,7 +41,7 @@ impl State for CoinsPanel {
&Menu::Coins,
cache,
self.warning.as_ref(),
view::coins::coins_view(cache, &self.coins, self.timelock),
view::coins::coins_view(cache, &self.coins, self.timelock, &self.selected),
)
}
@ -56,6 +55,7 @@ impl State for CoinsPanel {
Message::Coins(res) => match res {
Err(e) => self.warning = Some(e),
Ok(coins) => {
self.selected = Vec::new();
self.warning = None;
self.coins = coins
.iter()
@ -69,11 +69,12 @@ impl State for CoinsPanel {
.collect();
}
},
Message::View(view::Message::Close) => {
self.selected_coin = None;
}
Message::View(view::Message::Select(i)) => {
self.selected_coin = Some(i);
if let Some(position) = self.selected.iter().position(|j| *j == i) {
self.selected.remove(position);
} else {
self.selected.push(i);
}
}
_ => {}
};

View File

@ -6,8 +6,8 @@ use std::convert::TryInto;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use iced::pure::{column, Element};
use iced::{widget::qr_code, Command, Subscription};
use iced::{widget::Column, Element};
use liana::miniscript::bitcoin::{Address, Amount};
use super::{cache::Cache, error::Error, menu::Menu, message::Message, view};
@ -241,7 +241,7 @@ impl State for ReceivePanel {
view::receive::receive(address, self.qr_code.as_ref().unwrap()),
)
} else {
view::dashboard(&Menu::Receive, cache, self.warning.as_ref(), column())
view::dashboard(&Menu::Receive, cache, self.warning.as_ref(), Column::new())
}
}
fn update(

View File

@ -5,7 +5,7 @@ use std::str::FromStr;
use std::sync::Arc;
use chrono::prelude::*;
use iced::{pure::Element, Command};
use iced::{Command, Element};
use liana::config::Config;

View File

@ -1,7 +1,6 @@
use std::sync::Arc;
use iced::pure::Element;
use iced::Command;
use iced::{Command, Element};
use liana::miniscript::bitcoin::util::{bip32::Fingerprint, psbt::Psbt};
use crate::{
@ -364,6 +363,6 @@ pub struct NoAction {}
impl Action for NoAction {
fn view(&self) -> Element<view::Message> {
iced::pure::column().into()
iced::widget::Column::new().into()
}
}

View File

@ -2,7 +2,7 @@ mod detail;
mod step;
use std::sync::Arc;
use iced::{pure::Element, Command};
use iced::{Command, Element};
use super::{redirect, State};
use crate::{

View File

@ -2,8 +2,7 @@ use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use iced::pure::Element;
use iced::Command;
use iced::{Command, Element};
use liana::miniscript::bitcoin::{
util::psbt::Psbt, Address, Amount, Denomination, OutPoint, Script,
};

View File

@ -1,11 +1,11 @@
use iced::{
pure::{column, container, row, Element},
Alignment, Length,
widget::{Button, Column, Container, Row},
Alignment, Element, Length,
};
use crate::ui::{
color,
component::{badge, card, collapse::collapse, separation, text::*},
component::{badge, button, card, separation, text::*},
icon,
util::Collection,
};
@ -15,22 +15,36 @@ use crate::{
daemon::model::Coin,
};
pub fn coins_view<'a>(cache: &Cache, coins: &'a [Coin], timelock: u32) -> Element<'a, Message> {
column()
pub fn coins_view<'a>(
cache: &Cache,
coins: &'a [Coin],
timelock: u32,
selected: &[usize],
) -> Element<'a, Message> {
Column::new()
.push(
container(
row()
.push(text(&format!(" {}", coins.len())).bold())
Container::new(
Row::new()
.push(text(format!(" {}", coins.len())))
.push(text(" coins")),
)
.width(Length::Fill),
)
.push(
column()
Column::new()
.spacing(10)
.push(coins.iter().fold(column().spacing(10), |col, coin| {
col.push(coin_list_view(coin, timelock, cache.blockheight as u32))
})),
.push(coins.iter().enumerate().fold(
Column::new().spacing(10),
|col, (i, coin)| {
col.push(coin_list_view(
coin,
timelock,
cache.blockheight as u32,
i,
selected.contains(&i),
))
},
)),
)
.align_items(Alignment::Center)
.spacing(20)
@ -38,141 +52,154 @@ pub fn coins_view<'a>(cache: &Cache, coins: &'a [Coin], timelock: u32) -> Elemen
}
#[allow(clippy::collapsible_else_if)]
fn coin_list_view(coin: &Coin, timelock: u32, blockheight: u32) -> Element<Message> {
container(collapse::<_, _, _, _, _>(
move || {
row::<Message, _>()
.push(
row()
.push(badge::coin())
.push_maybe(if coin.spend_info.is_some() {
Some(
container(text(" Spent ").small())
.padding(3)
.style(badge::PillStyle::Success),
)
} else {
if let Some(b) = coin.block_height {
if blockheight > b as u32 + timelock {
Some(container(
row()
.spacing(5)
.push(text(" 0").small().color(color::ALERT))
.push(
icon::hourglass_done_icon()
.small()
.color(color::ALERT),
)
.align_items(Alignment::Center),
))
} else {
Some(container(
row()
.spacing(5)
.push(
text(&format!(
" {}",
b as u32 + timelock - blockheight
))
.small(),
)
.push(icon::hourglass_icon().small())
.align_items(Alignment::Center),
))
}
} else {
None
}
})
.spacing(10)
.align_items(Alignment::Center)
.width(Length::Fill),
)
.push(
row()
.spacing(5)
fn coin_list_view(
coin: &Coin,
timelock: u32,
blockheight: u32,
index: usize,
collapsed: bool,
) -> Container<Message> {
Container::new(
Column::new()
.push(
Button::new(
Row::new()
.push(
text(&format!("{:.8}", coin.amount.to_btc()))
.bold()
.width(Length::Shrink),
Row::new()
.push(badge::coin())
.push_maybe(if coin.spend_info.is_some() {
Some(
Container::new(text(" Spent ").small())
.padding(3)
.style(badge::PillStyle::Success),
)
} else {
if let Some(b) = coin.block_height {
if blockheight > b as u32 + timelock {
Some(Container::new(
Row::new()
.spacing(5)
.push(text(" 0").small().style(color::ALERT))
.push(
icon::hourglass_done_icon()
.small()
.style(color::ALERT),
)
.align_items(Alignment::Center),
))
} else {
Some(Container::new(
Row::new()
.spacing(5)
.push(
text(format!(
" {}",
b as u32 + timelock - blockheight
))
.small(),
)
.push(icon::hourglass_icon().small())
.align_items(Alignment::Center),
))
}
} else {
None
}
})
.spacing(10)
.align_items(Alignment::Center)
.width(Length::Fill),
)
.push(text("BTC"))
.align_items(Alignment::Center),
)
.align_items(Alignment::Center)
.spacing(20)
.into()
},
move || {
column()
.spacing(10)
.push(separation().width(Length::Fill))
.push(
column()
.padding(10)
.spacing(5)
.push_maybe(if coin.spend_info.is_none() {
if let Some(b) = coin.block_height {
if blockheight > b as u32 + timelock {
Some(container(
text("The recovery path is available")
.bold()
.small()
.color(color::ALERT),
))
} else {
Some(container(
text(&format!(
"The recovery path will be available in {} blocks",
b as u32 + timelock - blockheight
))
.bold()
.small(),
))
}
} else {
None
}
} else {
None
})
.push(
column()
Row::new()
.spacing(5)
.push(
row()
.push(text("Outpoint:").small().bold())
.push(text(&format!("{}", coin.outpoint)).small())
.spacing(5),
text(format!("{:.8}", coin.amount.to_btc()))
.bold()
.width(Length::Shrink),
)
.push_maybe(coin.block_height.map(|b| {
row()
.push(text("Block height:").small().bold())
.push(text(&format!("{}", b)).small())
.push(text("BTC"))
.align_items(Alignment::Center),
)
.align_items(Alignment::Center)
.spacing(20),
)
.style(button::Style::TransparentBorder.into())
.padding(10)
.on_press(Message::Select(index)),
)
.push_maybe(if collapsed {
Some(
Column::new()
.spacing(10)
.push(separation().width(Length::Fill))
.push(
Column::new()
.padding(10)
.spacing(5)
.push_maybe(if coin.spend_info.is_none() {
if let Some(b) = coin.block_height {
if blockheight > b as u32 + timelock {
Some(Container::new(
text("The recovery path is available")
.bold()
.small()
.style(color::ALERT),
))
} else {
Some(Container::new(
text(format!(
"The recovery path will be available in {} blocks",
b as u32 + timelock - blockheight
))
.bold()
.small(),
))
}
} else {
None
}
} else {
None
})
.push(
Column::new()
.push(
Row::new()
.push(text("Outpoint:").small().bold())
.push(text(format!("{}", coin.outpoint)).small())
.spacing(5),
)
.push_maybe(coin.block_height.map(|b| {
Row::new()
.push(text("Block height:").small().bold())
.push(text(format!("{}", b)).small())
.spacing(5)
})),
)
.push_maybe(coin.spend_info.map(|info| {
Column::new()
.push(
Row::new()
.push(text("Spend txid:").small().bold())
.push(text(format!("{}", info.txid)).small())
.spacing(5),
)
.push(if let Some(height) = info.height {
Row::new()
.push(text("Spend block height:").small().bold())
.push(text(format!("{}", height)).small())
.spacing(5)
} else {
Row::new().push(text("Not in a block").bold().small())
})
.spacing(5)
})),
)
.push_maybe(coin.spend_info.map(|info| {
column()
.push(
row()
.push(text("Spend txid:").small().bold())
.push(text(&format!("{}", info.txid)).small())
.spacing(5),
)
.push(if let Some(height) = info.height {
row()
.push(text("Spend block height:").small().bold())
.push(text(&format!("{}", height)).small())
.spacing(5)
} else {
row().push(text("Not in a block").bold().small())
})
.spacing(5)
})),
),
)
.into()
},
))
} else {
None
}),
)
.style(card::SimpleCardStyle)
.into()
}

View File

@ -2,8 +2,8 @@ use chrono::NaiveDateTime;
use iced::{
alignment,
pure::{button, column, container, row, Element},
Alignment, Length,
widget::{Button, Column, Container, Row},
Alignment, Element, Length,
};
use crate::ui::{
@ -21,17 +21,17 @@ pub fn home_view<'a>(
pending_events: &[HistoryTransaction],
events: &Vec<HistoryTransaction>,
) -> Element<'a, Message> {
column()
.push(column().padding(40))
.push(text(&format!("{} BTC", balance.to_btc())).bold().size(50))
Column::new()
.push(Column::new().padding(40))
.push(text(format!("{} BTC", balance.to_btc())).bold().size(50))
.push(
column()
Column::new()
.spacing(10)
.push(
pending_events
.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, event)| {
.fold(Column::new().spacing(10), |col, (i, event)| {
col.push(event_list_view(i, event))
}),
)
@ -39,22 +39,22 @@ pub fn home_view<'a>(
events
.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, event)| {
.fold(Column::new().spacing(10), |col, (i, event)| {
col.push(event_list_view(i, event))
}),
)
.push_maybe(
if events.len() % HISTORY_EVENT_PAGE_SIZE as usize == 0 && !events.is_empty() {
Some(
container(
button(
Container::new(
Button::new(
text("See more")
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
.width(Length::Fill)
.padding(15)
.style(Style::TransparentBorder)
.style(Style::TransparentBorder.into())
.on_press(Message::Next),
)
.width(Length::Fill)
@ -71,23 +71,23 @@ pub fn home_view<'a>(
}
fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Element<'a, Message> {
container(
button(
row()
Container::new(
Button::new(
Row::new()
.push(
row()
Row::new()
.push(if event.is_external() {
badge::receive()
} else {
badge::spend()
})
.push(if let Some(t) = event.time {
container(
text(&format!("{}", NaiveDateTime::from_timestamp(t as i64, 0)))
Container::new(
text(format!("{}", NaiveDateTime::from_timestamp(t as i64, 0)))
.small(),
)
} else {
container(text(" Pending ").small())
Container::new(text(" Pending ").small())
.padding(3)
.style(badge::PillStyle::Success)
})
@ -96,9 +96,9 @@ fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Element<'a, Mess
.width(Length::Fill),
)
.push(
row()
Row::new()
.push(
text(&{
text({
if event.is_external() {
format!("+ {:.8}", event.incoming_amount.to_btc())
} else {
@ -117,16 +117,16 @@ fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Element<'a, Mess
)
.padding(10)
.on_press(Message::Select(i))
.style(Style::TransparentBorder),
.style(Style::TransparentBorder.into()),
)
.style(card::SimpleCardStyle)
.into()
}
pub fn event_view<'a>(event: &HistoryTransaction) -> Element<'a, Message> {
column()
Column::new()
.push(
row()
Row::new()
.push(if event.is_external() {
badge::receive()
} else {
@ -136,7 +136,7 @@ pub fn event_view<'a>(event: &HistoryTransaction) -> Element<'a, Message> {
.align_items(Alignment::Center),
)
.push(
text(&{
text({
if event.is_external() {
format!("+ {} BTC", event.incoming_amount.to_btc())
} else {
@ -150,23 +150,24 @@ pub fn event_view<'a>(event: &HistoryTransaction) -> Element<'a, Message> {
.push_maybe(
event
.fee_amount
.map(|fee| container(text(&format!("Miner Fee: {} BTC", fee.to_btc())))),
.map(|fee| Container::new(text(format!("Miner Fee: {} BTC", fee.to_btc())))),
)
.push(card::simple(
column()
Column::new()
.push_maybe(event.time.map(|t| {
let date = NaiveDateTime::from_timestamp(t as i64, 0);
row()
Row::new()
.width(Length::Fill)
.push(container(text("Date:").bold()).width(Length::Fill))
.push(container(text(&format!("{}", date))).width(Length::Shrink))
.push(Container::new(text("Date:").bold()).width(Length::Fill))
.push(Container::new(text(format!("{}", date))).width(Length::Shrink))
}))
.push(
row()
Row::new()
.width(Length::Fill)
.push(container(text("Txid:").bold()).width(Length::Fill))
.push(Container::new(text("Txid:").bold()).width(Length::Fill))
.push(
container(text(&format!("{}", event.tx.txid()))).width(Length::Shrink),
Container::new(text(format!("{}", event.tx.txid())))
.width(Length::Shrink),
),
)
.spacing(5),

View File

@ -11,13 +11,12 @@ pub use message::*;
use warning::warn;
use iced::{
pure::{column, container, row, scrollable, widget, Element},
Length,
widget::{self, scrollable, Button, Column, Container, Row},
Element, Length,
};
use crate::ui::{
color,
component::{badge, button, separation, text::*},
component::{badge, button, container, separation, text::*},
icon::{coin_icon, cross_icon, home_icon, receive_icon, send_icon, settings_icon},
util::Collection,
};
@ -36,11 +35,11 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
};
let coins_button = if *menu == Menu::Coins {
iced::pure::widget::button::Button::new(
container(
row()
Button::new(
Container::new(
Row::new()
.push(
row()
Row::new()
.push(coin_icon())
.push(text("Coins"))
.spacing(10)
@ -48,8 +47,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.align_items(iced::Alignment::Center),
)
.push(
container(
text(&format!(
Container::new(
text(format!(
" {} ",
cache
.coins
@ -71,15 +70,15 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Primary)
.style(button::Style::Primary.into())
.on_press(Message::Reload)
.width(iced::Length::Units(200))
} else {
iced::pure::widget::button::Button::new(
container(
row()
Button::new(
Container::new(
Row::new()
.push(
row()
Row::new()
.push(coin_icon())
.push(text("Coins"))
.spacing(10)
@ -87,8 +86,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.align_items(iced::Alignment::Center),
)
.push(
container(
text(&format!(
Container::new(
text(format!(
" {} ",
cache
.coins
@ -110,17 +109,17 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Transparent)
.style(button::Style::Transparent.into())
.on_press(Message::Menu(Menu::Coins))
.width(iced::Length::Units(200))
};
let spend_button = if *menu == Menu::Spend {
iced::pure::widget::button::Button::new(
container(
row()
Button::new(
Container::new(
Row::new()
.push(
row()
Row::new()
.push(send_icon())
.push(text("Send"))
.spacing(10)
@ -131,8 +130,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
None
} else {
Some(
container(
text(&format!(" {} ", cache.spend_txs.len()))
Container::new(
text(format!(" {} ", cache.spend_txs.len()))
.small()
.bold(),
)
@ -147,15 +146,15 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Primary)
.style(button::Style::Primary.into())
.on_press(Message::Reload)
.width(iced::Length::Units(200))
} else {
iced::pure::widget::button::Button::new(
container(
row()
Button::new(
Container::new(
Row::new()
.push(
row()
Row::new()
.push(send_icon())
.push(text("Send"))
.spacing(10)
@ -166,8 +165,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
None
} else {
Some(
container(
text(&format!(" {} ", cache.spend_txs.len()))
Container::new(
text(format!(" {} ", cache.spend_txs.len()))
.small()
.bold(),
)
@ -182,7 +181,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Transparent)
.style(button::Style::Transparent.into())
.on_press(Message::Menu(Menu::Spend))
.width(iced::Length::Units(200))
};
@ -207,14 +206,14 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.width(iced::Length::Units(200))
};
container(
column()
Container::new(
Column::new()
.padding(10)
.push(
column()
Column::new()
.push(
column()
.push(container(text("Liana").bold()).padding(10))
Column::new()
.push(Container::new(text("Liana").bold()).padding(10))
.push(separation().width(Length::Units(200)))
.spacing(10),
)
@ -226,11 +225,11 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.height(Length::Fill),
)
.push(
container(
column()
Container::new(
Column::new()
.spacing(10)
.push_maybe(cache.rescan_progress.map(|p| {
container(text(&format!(" Rescan...{:.2}% ", p * 100.0)))
Container::new(text(format!(" Rescan...{:.2}% ", p * 100.0)))
.padding(5)
.style(badge::PillStyle::Simple)
}))
@ -239,19 +238,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.height(Length::Shrink),
),
)
.style(SidebarStyle)
}
pub struct SidebarStyle;
impl widget::container::StyleSheet for SidebarStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
background: color::FOREGROUND.into(),
border_width: 1.0,
border_color: color::SECONDARY,
..widget::container::Style::default()
}
}
.style(container::Style::Sidebar)
}
pub fn dashboard<'a, T: Into<Element<'a, Message>>>(
@ -260,17 +247,17 @@ pub fn dashboard<'a, T: Into<Element<'a, Message>>>(
warning: Option<&Error>,
content: T,
) -> Element<'a, Message> {
row()
Row::new()
.push(
sidebar(menu, cache)
.width(Length::Shrink)
.height(Length::Fill),
)
.push(
column()
Column::new()
.push(warn(warning))
.push(main_section(container(scrollable(
container(content).padding(20),
.push(main_section(Container::new(scrollable(
Container::new(content).padding(20),
)))),
)
.width(iced::Length::Fill)
@ -279,69 +266,49 @@ pub fn dashboard<'a, T: Into<Element<'a, Message>>>(
}
fn main_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<'a, T> {
container(menu.max_width(1500))
.style(MainSectionStyle)
Container::new(menu.max_width(1500))
.style(container::Style::Background)
.center_x()
.width(Length::Fill)
.height(Length::Fill)
}
pub struct MainSectionStyle;
impl widget::container::StyleSheet for MainSectionStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
background: color::BACKGROUND.into(),
..widget::container::Style::default()
}
}
}
pub fn modal<'a, T: Into<Element<'a, Message>>>(
is_previous: bool,
warning: Option<&Error>,
content: T,
) -> Element<'a, Message> {
column()
Column::new()
.push(warn(warning))
.push(
container(
row()
Container::new(
Row::new()
.push(if is_previous {
column()
Column::new()
.push(
button::transparent(None, "< Previous").on_press(Message::Previous),
)
.width(Length::Fill)
} else {
column().width(Length::Fill)
Column::new().width(Length::Fill)
})
.align_items(iced::Alignment::Center)
.push(button::primary(Some(cross_icon()), "Close").on_press(Message::Close)),
)
.padding(10)
.style(ModalSectionStyle),
.style(container::Style::Background),
)
.push(modal_section(container(scrollable(content))))
.push(modal_section(Container::new(scrollable(content))))
.width(Length::Fill)
.height(Length::Fill)
.into()
}
fn modal_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<'a, T> {
container(menu.max_width(1500))
Container::new(menu.max_width(1500))
.padding(20)
.style(ModalSectionStyle)
.style(container::Style::Background)
.center_x()
.width(Length::Fill)
.height(Length::Fill)
}
pub struct ModalSectionStyle;
impl widget::container::StyleSheet for ModalSectionStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
background: color::BACKGROUND.into(),
..widget::container::Style::default()
}
}
}

View File

@ -1,7 +1,9 @@
use iced::{
pure::{column, row, widget::Button, Element},
widget::qr_code::{self, QRCode},
Alignment,
widget::{
qr_code::{self, QRCode},
Button, Column, Row,
},
Alignment, Element,
};
use liana::miniscript::bitcoin;
@ -15,15 +17,15 @@ use super::message::Message;
pub fn receive<'a>(address: &'a bitcoin::Address, qr: &'a qr_code::State) -> Element<'a, Message> {
card::simple(
column()
Column::new()
.push(QRCode::new(qr).cell_size(10))
.push(
row()
.push(text(&address.to_string()).small())
Row::new()
.push(text(address.to_string()).small())
.push(
Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(address.to_string()))
.style(button::Style::TransparentBorder),
.style(button::Style::TransparentBorder.into()),
)
.align_items(Alignment::Center),
)

View File

@ -2,8 +2,8 @@ use std::str::FromStr;
use iced::{
alignment,
pure::{column, container, progress_bar, row, widget, Element},
Alignment, Length,
widget::{self, Column, Container, ProgressBar, Row},
Alignment, Element, Length,
};
use liana::miniscript::bitcoin;
@ -43,29 +43,29 @@ pub fn bitcoind_edit<'a>(
cookie_path: &form::Value<String>,
processing: bool,
) -> Element<'a, SettingsMessage> {
let mut col = column().spacing(20);
let mut col = Column::new().spacing(20);
if blockheight != 0 {
col = col
.push(
row()
Row::new()
.push(
row()
Row::new()
.push(badge::Badge::new(icon::network_icon()))
.push(
column()
Column::new()
.push(text("Network:"))
.push(text(&network.to_string()).bold()),
.push(text(network.to_string()).bold()),
)
.spacing(10)
.width(Length::FillPortion(1)),
)
.push(
row()
Row::new()
.push(badge::Badge::new(icon::block_icon()))
.push(
column()
Column::new()
.push(text("Block Height:"))
.push(text(&blockheight.to_string()).bold()),
.push(text(blockheight.to_string()).bold()),
)
.spacing(10)
.width(Length::FillPortion(1)),
@ -76,7 +76,7 @@ pub fn bitcoind_edit<'a>(
col = col
.push(
column()
Column::new()
.push(text("Cookie file path:").bold().small())
.push(
form::Form::new("Cookie file path", cookie_path, |value| {
@ -89,7 +89,7 @@ pub fn bitcoind_edit<'a>(
.spacing(5),
)
.push(
column()
Column::new()
.push(text("Socket address:").bold().small())
.push(
form::Form::new("Socket address:", addr, |value| {
@ -109,10 +109,10 @@ pub fn bitcoind_edit<'a>(
confirm_button = confirm_button.on_press(SettingsMessage::ConfirmEdit);
}
card::simple(container(
column()
card::simple(Container::new(
Column::new()
.push(
row()
Row::new()
.push(badge::Badge::new(icon::bitcoin_icon()))
.push(text("Bitcoind"))
.padding(10)
@ -123,8 +123,8 @@ pub fn bitcoind_edit<'a>(
.push(separation().width(Length::Fill))
.push(col)
.push(
container(
row()
Container::new(
Row::new()
.push(cancel_button)
.push(confirm_button)
.spacing(10)
@ -146,29 +146,29 @@ pub fn bitcoind<'a>(
is_running: Option<bool>,
can_edit: bool,
) -> Element<'a, SettingsMessage> {
let mut col = column().spacing(20);
let mut col = Column::new().spacing(20);
if blockheight != 0 {
col = col
.push(
row()
Row::new()
.push(
row()
Row::new()
.push(badge::Badge::new(icon::network_icon()))
.push(
column()
Column::new()
.push(text("Network:"))
.push(text(&network.to_string()).bold()),
.push(text(network.to_string()).bold()),
)
.spacing(10)
.width(Length::FillPortion(1)),
)
.push(
row()
Row::new()
.push(badge::Badge::new(icon::block_icon()))
.push(
column()
Column::new()
.push(text("Block Height:"))
.push(text(&blockheight.to_string()).bold()),
.push(text(blockheight.to_string()).bold()),
)
.spacing(10)
.width(Length::FillPortion(1)),
@ -185,21 +185,21 @@ pub fn bitcoind<'a>(
("Socket address:", config.addr.to_string()),
];
let mut col_fields = column();
let mut col_fields = Column::new();
for (k, v) in rows {
col_fields = col_fields.push(
row()
.push(container(text(k).bold().small()).width(Length::Fill))
.push(text(&v).small()),
Row::new()
.push(Container::new(text(k).bold().small()).width(Length::Fill))
.push(text(v).small()),
);
}
card::simple(container(
column()
card::simple(Container::new(
Column::new()
.push(
row()
Row::new()
.push(
row()
Row::new()
.push(badge::Badge::new(icon::bitcoin_icon()))
.push(text("Bitcoind"))
.push(is_running_label(is_running))
@ -209,11 +209,11 @@ pub fn bitcoind<'a>(
)
.push(if can_edit {
widget::Button::new(icon::pencil_icon())
.style(button::Style::TransparentBorder)
.style(button::Style::TransparentBorder.into())
.on_press(SettingsMessage::Edit)
} else {
widget::Button::new(icon::pencil_icon())
.style(button::Style::TransparentBorder)
.style(button::Style::TransparentBorder.into())
})
.align_items(Alignment::Center),
)
@ -228,22 +228,22 @@ pub fn bitcoind<'a>(
pub fn is_running_label<'a, T: 'a>(is_running: Option<bool>) -> widget::Container<'a, T> {
if let Some(running) = is_running {
if running {
container(
row()
.push(icon::dot_icon().size(5).color(color::SUCCESS))
.push(text("Running").small().color(color::SUCCESS))
Container::new(
Row::new()
.push(icon::dot_icon().size(5).style(color::SUCCESS))
.push(text("Running").small().style(color::SUCCESS))
.align_items(Alignment::Center),
)
} else {
container(
row()
.push(icon::dot_icon().size(5).color(color::ALERT))
.push(text("Not running").small().color(color::ALERT))
Container::new(
Row::new()
.push(icon::dot_icon().size(5).style(color::ALERT))
.push(text("Not running").small().style(color::ALERT))
.align_items(Alignment::Center),
)
}
} else {
container(column())
Container::new(Column::new())
}
}
@ -256,14 +256,14 @@ pub fn rescan<'a>(
processing: bool,
can_edit: bool,
) -> Element<'a, SettingsMessage> {
card::simple(container(
column()
card::simple(Container::new(
Column::new()
.push(
row()
Row::new()
.push(badge::Badge::new(icon::block_icon()))
.push(text("Rescan blockchain").width(Length::Fill))
.push_maybe(if success {
Some(text("Rescan was successful").color(color::SUCCESS))
Some(text("Rescan was successful").style(color::SUCCESS))
} else {
None
})
@ -273,18 +273,18 @@ pub fn rescan<'a>(
)
.push(separation().width(Length::Fill))
.push(if let Some(p) = scan_progress {
container(
column()
Container::new(
Column::new()
.width(Length::Fill)
.push(progress_bar(0.0..=1.0, p as f32).width(Length::Fill))
.push(text(&format!("Rescan...{:.2}%", p * 100.0))),
.push(ProgressBar::new(0.0..=1.0, p as f32).width(Length::Fill))
.push(text(format!("Rescan...{:.2}%", p * 100.0))),
)
} else {
container(
column()
Container::new(
Column::new()
.spacing(10)
.push(
row()
Row::new()
.push(text("Year:").bold().small())
.push(
form::Form::new("2022", year, |value| {
@ -321,18 +321,18 @@ pub fn rescan<'a>(
})
&& is_ok_and(&u32::from_str(&day.value), |&v| v > 0 && v <= 31))
{
row().push(column().width(Length::Fill)).push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(None, "Start rescan")
.on_press(SettingsMessage::ConfirmEdit)
.width(Length::Shrink),
)
} else if processing {
row().push(column().width(Length::Fill)).push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(None, "Starting rescan...")
.width(Length::Shrink),
)
} else {
row().push(column().width(Length::Fill)).push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(None, "Start rescan").width(Length::Shrink),
)
},

View File

@ -1,6 +1,6 @@
use iced::{
pure::{column, container, row, scrollable, Element},
Alignment, Length,
widget::{Button, Column, Container, Row, Scrollable},
Alignment, Element, Length,
};
use liana::miniscript::bitcoin::{
@ -11,14 +11,14 @@ use liana::miniscript::bitcoin::{
use crate::{
app::{
error::Error,
view::{message::*, modal_section, warning::warn, ModalSectionStyle},
view::{message::*, modal_section, warning::warn},
},
daemon::model::{Coin, SpendStatus, SpendTx},
hw::HardwareWallet,
ui::{
color,
component::{
badge, button, card, separation,
badge, button, card, container, separation,
text::{text, Text},
},
icon,
@ -36,7 +36,7 @@ pub fn spend_view<'a, T: Into<Element<'a, Message>>>(
spend_modal(
show_delete,
warning,
column()
Column::new()
.align_items(Alignment::Center)
.spacing(20)
.push(spend_header(tx))
@ -59,10 +59,10 @@ pub fn save_action<'a>(saved: bool) -> Element<'a, Message> {
.into()
} else {
card::simple(
column()
Column::new()
.spacing(10)
.push(text("Save the transaction"))
.push(row().push(column().width(Length::Fill)).push(
.push(Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(None, "Save").on_press(Message::Spend(SpendTxMessage::Confirm)),
)),
)
@ -79,11 +79,11 @@ pub fn broadcast_action<'a>(saved: bool) -> Element<'a, Message> {
.into()
} else {
card::simple(
column()
Column::new()
.spacing(10)
.push(text("Broadcast the transaction"))
.push(
row().push(column().width(Length::Fill)).push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(None, "Broadcast")
.on_press(Message::Spend(SpendTxMessage::Confirm)),
),
@ -102,12 +102,12 @@ pub fn delete_action<'a>(deleted: bool) -> Element<'a, Message> {
.into()
} else {
card::simple(
column()
Column::new()
.spacing(10)
.push(text("Delete the transaction draft"))
.push(
row()
.push(column().width(Length::Fill))
Row::new()
.push(Column::new().width(Length::Fill))
.push(
button::transparent(None, "Cancel")
.on_press(Message::Spend(SpendTxMessage::Cancel)),
@ -128,20 +128,20 @@ pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
warning: Option<&Error>,
content: T,
) -> Element<'a, Message> {
column()
Column::new()
.push(warn(warning))
.push(
container(
row()
Container::new(
Row::new()
.push(if show_delete {
column()
Column::new()
.push(
button::alert(Some(icon::trash_icon()), "Delete")
.on_press(Message::Spend(SpendTxMessage::Delete)),
)
.width(Length::Fill)
} else {
column()
Column::new()
.push(
button::transparent(None, "< Previous").on_press(Message::Previous),
)
@ -153,20 +153,20 @@ pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
),
)
.padding(10)
.style(ModalSectionStyle),
.style(container::Style::Background),
)
.push(modal_section(container(scrollable(content))))
.push(modal_section(Container::new(Scrollable::new(content))))
.width(Length::Fill)
.height(Length::Fill)
.into()
}
fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
column()
Column::new()
.spacing(20)
.align_items(Alignment::Center)
.push(
row()
Row::new()
.push(badge::Badge::new(icon::send_icon()).style(badge::Style::Standard))
.push(text("Spend").bold())
.spacing(5)
@ -174,26 +174,26 @@ fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
)
.push_maybe(match tx.status {
SpendStatus::Deprecated => Some(
container(text(" Deprecated ").small())
Container::new(text(" Deprecated ").small())
.padding(3)
.style(badge::PillStyle::Simple),
),
SpendStatus::Broadcasted => Some(
container(text(" Broadcasted ").small())
Container::new(text(" Broadcasted ").small())
.padding(3)
.style(badge::PillStyle::Success),
),
_ => None,
})
.push(
column()
Column::new()
.align_items(Alignment::Center)
.push(
text(&format!("- {} BTC", tx.spend_amount.to_btc()))
text(format!("- {} BTC", tx.spend_amount.to_btc()))
.bold()
.size(50),
)
.push(container(text(&format!(
.push(Container::new(text(format!(
"Miner Fee: {} BTC",
tx.fee_amount.to_btc()
)))),
@ -203,16 +203,23 @@ fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
fn spend_overview_view<'a>(tx: &SpendTx) -> Element<'a, Message> {
card::simple(
column()
.push(container(
row()
Column::new()
.push(Container::new(
Row::new()
.push(
container(
row()
.push(container(icon::key_icon().size(30).width(Length::Fill)))
.push(column().push(text("Number of signatures:").bold()).push(
text(&format!("{}", tx.psbt.inputs[0].partial_sigs.len(),)),
Container::new(
Row::new()
.push(Container::new(
icon::key_icon().size(30).width(Length::Fill),
))
.push(
Column::new()
.push(text("Number of signatures:").bold())
.push(text(format!(
"{}",
tx.psbt.inputs[0].partial_sigs.len(),
))),
)
.align_items(Alignment::Center)
.spacing(20),
)
@ -223,15 +230,15 @@ fn spend_overview_view<'a>(tx: &SpendTx) -> Element<'a, Message> {
))
.push(separation().width(Length::Fill))
.push(
column()
Column::new()
.push(
row()
Row::new()
.push(text("Tx ID:").bold().width(Length::Fill))
.push(text(&format!("{}", tx.psbt.unsigned_tx.txid())).small())
.push(text(format!("{}", tx.psbt.unsigned_tx.txid())).small())
.align_items(Alignment::Center),
)
.push(
row()
Row::new()
.push(text("Psbt:").bold().width(Length::Fill))
.push(
button::transparent(Some(icon::clipboard_icon()), "Copy")
@ -251,21 +258,21 @@ fn inputs_and_outputs_view<'a>(
network: Network,
change_index: Option<usize>,
) -> Element<'a, Message> {
column()
Column::new()
.push(
row()
Row::new()
.spacing(10)
.push(
column()
Column::new()
.spacing(10)
.push(text("Spent coins:").bold())
.push(coins.iter().fold(column().spacing(10), |col, coin| {
.push(coins.iter().fold(Column::new().spacing(10), |col, coin| {
col.push(
card::simple(
column()
Column::new()
.width(Length::Fill)
.push(text(&format!("{} BTC", coin.amount.to_btc())).bold())
.push(text(&format!("{}", coin.outpoint)).small()),
.push(text(format!("{} BTC", coin.amount.to_btc())).bold())
.push(text(format!("{}", coin.outpoint)).small()),
)
.width(Length::Fill),
)
@ -273,28 +280,28 @@ fn inputs_and_outputs_view<'a>(
.width(Length::FillPortion(1)),
)
.push(
column()
Column::new()
.spacing(10)
.push(text("Recipients:").bold())
.push(psbt.unsigned_tx.output.iter().enumerate().fold(
column().spacing(10),
Column::new().spacing(10),
|col, (i, output)| {
col.push(
card::simple(
column()
Column::new()
.spacing(10)
.push(
column()
Column::new()
.width(Length::Fill)
.push(
text(&format!(
text(format!(
"{} BTC",
Amount::from_sat(output.value).to_btc()
))
.bold(),
)
.push(
text(&format!(
text(format!(
"{}",
Address::from_script(
&output.script_pubkey,
@ -307,7 +314,7 @@ fn inputs_and_outputs_view<'a>(
)
.push_maybe(if Some(i) == change_index {
Some(
container(text("Change"))
Container::new(text("Change"))
.padding(5)
.style(badge::PillStyle::Success),
)
@ -332,15 +339,15 @@ pub fn sign_action<'a>(
signed: &[Fingerprint],
) -> Element<'a, Message> {
card::simple(
column()
Column::new()
.push(if !hws.is_empty() {
column()
Column::new()
.push(text("Select hardware wallet to sign with:").bold())
.spacing(10)
.push(
hws.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, hw)| {
.fold(Column::new().spacing(10), |col, (i, hw)| {
col.push(hw_list_view(
i,
hw,
@ -352,10 +359,10 @@ pub fn sign_action<'a>(
)
.width(Length::Fill)
} else {
column()
Column::new()
.push(
card::simple(
column()
Column::new()
.spacing(20)
.width(Length::Fill)
.push("Please connect a hardware wallet")
@ -380,18 +387,18 @@ fn hw_list_view<'a>(
processing: bool,
signed: bool,
) -> Element<'a, Message> {
let mut bttn = iced::pure::button(
row()
let mut bttn = Button::new(
Row::new()
.push(
column()
.push(text(&format!("{}", hw.kind)).bold())
.push(text(&format!("fingerprint: {}", hw.fingerprint)).small())
Column::new()
.push(text(format!("{}", hw.kind)).bold())
.push(text(format!("fingerprint: {}", hw.fingerprint)).small())
.spacing(5)
.width(Length::Fill),
)
.push_maybe(if chosen && processing {
Some(
column()
Column::new()
.push(text("Processing..."))
.push(text("Please check your device").small()),
)
@ -400,11 +407,11 @@ fn hw_list_view<'a>(
})
.push_maybe(if signed {
Some(
column().push(
row()
Column::new().push(
Row::new()
.spacing(5)
.push(icon::circle_check_icon().color(color::SUCCESS))
.push(text("Signed").color(color::SUCCESS)),
.push(icon::circle_check_icon().style(color::SUCCESS))
.push(text("Signed").style(color::SUCCESS)),
),
)
} else {
@ -414,12 +421,12 @@ fn hw_list_view<'a>(
.width(Length::Fill),
)
.padding(10)
.style(button::Style::Border)
.style(button::Style::Border.into())
.width(Length::Fill);
if !processing {
bttn = bttn.on_press(Message::Spend(SpendTxMessage::SelectHardwareWallet(i)));
}
container(bttn)
Container::new(bttn)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.into()

View File

@ -2,8 +2,8 @@ pub mod detail;
pub mod step;
use iced::{
pure::{button, column, container, row, Element},
Alignment, Length,
widget::{Button, Column, Container, Row},
Alignment, Element, Length,
};
use crate::{
@ -19,27 +19,27 @@ use crate::{
use super::message::Message;
pub fn spend_view<'a>(spend_txs: &[SpendTx]) -> Element<'a, Message> {
column()
Column::new()
.push(
row().push(column().width(Length::Fill)).push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::primary(Some(icon::plus_icon()), "Create a new transaction")
.on_press(Message::Menu(Menu::CreateSpendTx)),
),
)
.push(
container(
row()
.push(text(&format!(" {}", spend_txs.len())).bold())
Container::new(
Row::new()
.push(text(format!(" {}", spend_txs.len())).bold())
.push(text(" draft transactions")),
)
.width(Length::Fill),
)
.push(
column().spacing(10).push(
Column::new().spacing(10).push(
spend_txs
.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, tx)| {
.fold(Column::new().spacing(10), |col, (i, tx)| {
col.push(spend_tx_list_view(i, tx))
}),
),
@ -50,20 +50,20 @@ pub fn spend_view<'a>(spend_txs: &[SpendTx]) -> Element<'a, Message> {
}
fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
container(
button(
row()
Container::new(
Button::new(
Row::new()
.push(
row()
Row::new()
.push(badge::spend())
.push_maybe(match tx.status {
SpendStatus::Deprecated => Some(
container(text(" Deprecated ").small())
Container::new(text(" Deprecated ").small())
.padding(3)
.style(badge::PillStyle::Simple),
),
SpendStatus::Broadcasted => Some(
container(text(" Broadcasted ").small())
Container::new(text(" Broadcasted ").small())
.padding(3)
.style(badge::PillStyle::Success),
),
@ -74,9 +74,9 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
.width(Length::Fill),
)
.push(
column()
.push(text(&format!("{} BTC", tx.spend_amount.to_btc())).bold())
.push(text(&format!("fee: {}", tx.fee_amount.to_btc())).small())
Column::new()
.push(text(format!("{} BTC", tx.spend_amount.to_btc())).bold())
.push(text(format!("fee: {}", tx.fee_amount.to_btc())).small())
.width(Length::Shrink),
)
.align_items(Alignment::Center)
@ -84,7 +84,7 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
)
.padding(10)
.on_press(Message::Select(i))
.style(button::Style::TransparentBorder),
.style(button::Style::TransparentBorder.into()),
)
.style(card::SimpleCardStyle)
.into()

View File

@ -1,6 +1,6 @@
use iced::{
pure::{column, container, row, widget, Element},
Alignment, Length,
widget::{self, Button, Column, Container, Row},
Alignment, Element, Length,
};
use liana::miniscript::bitcoin::Amount;
@ -28,10 +28,10 @@ pub fn choose_recipients_view(
modal(
false,
None,
column()
Column::new()
.push(text("Choose recipients").bold().size(50))
.push(
column()
Column::new()
.push(widget::Column::with_children(recipients).spacing(10))
.push(
button::transparent(Some(icon::plus_icon()), "Add recipient")
@ -59,7 +59,7 @@ pub fn recipient_view<'a>(
address: &form::Value<String>,
amount: &form::Value<String>,
) -> Element<'a, CreateSpendMessage> {
row()
Row::new()
.push(
form::Form::new("Address", address, move |msg| {
CreateSpendMessage::RecipientEdited(index, "address", msg)
@ -69,7 +69,7 @@ pub fn recipient_view<'a>(
.padding(10),
)
.push(
container(
Container::new(
form::Form::new("Amount", amount, move |msg| {
CreateSpendMessage::RecipientEdited(index, "amount", msg)
})
@ -97,10 +97,10 @@ pub fn choose_feerate_view<'a>(
modal(
true,
None,
column()
Column::new()
.push(text("Choose feerate").bold().size(50))
.push(
container(
Container::new(
form::Form::new("Feerate", feerate, move |msg| {
Message::CreateSpend(CreateSpendMessage::FeerateEdited(msg))
})
@ -110,7 +110,7 @@ pub fn choose_feerate_view<'a>(
)
.width(Length::Units(250)),
)
.push_maybe(error.map(|e| card::error("Failed to create spend", &e.to_string())))
.push_maybe(error.map(|e| card::error("Failed to create spend", e.to_string())))
.push_maybe(if is_valid {
Some(
button::primary(None, "Next")
@ -133,26 +133,26 @@ pub fn choose_coins_view<'a>(
modal(
true,
None,
column()
Column::new()
.push(text("Choose coins").bold().size(50))
.push(
column().spacing(10).push(
Column::new().spacing(10).push(
coins
.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, (coin, selected))| {
.fold(Column::new().spacing(10), |col, (i, (coin, selected))| {
col.push(coin_list_view(i, coin, *selected))
}),
),
)
.push_maybe(if is_valid {
Some(container(
Some(Container::new(
button::primary(None, "Next")
.on_press(Message::Next)
.width(Length::Units(100)),
))
} else if total_needed.is_some() {
Some(container(card::warning(&format!(
Some(Container::new(card::warning(format!(
"Total amount must be superior to {}",
total_needed.unwrap().to_btc(),
))))
@ -165,24 +165,24 @@ pub fn choose_coins_view<'a>(
}
fn coin_list_view<'a>(i: usize, coin: &Coin, selected: bool) -> Element<'a, Message> {
container(
iced::pure::button(
row()
Container::new(
Button::new(
Row::new()
.push(
row()
Row::new()
.push(if selected {
icon::square_check_icon()
} else {
icon::square_icon()
})
.push(badge::coin())
.push(text(&format!("block: {}", coin.block_height.unwrap_or(0))).small())
.push(text(format!("block: {}", coin.block_height.unwrap_or(0))).small())
.spacing(10)
.align_items(Alignment::Center)
.width(Length::Fill),
)
.push(
text(&format!("{} BTC", coin.amount.to_btc()))
text(format!("{} BTC", coin.amount.to_btc()))
.bold()
.width(Length::Shrink),
)
@ -191,7 +191,7 @@ fn coin_list_view<'a>(i: usize, coin: &Coin, selected: bool) -> Element<'a, Mess
)
.padding(10)
.on_press(Message::CreateSpend(CreateSpendMessage::SelectCoin(i)))
.style(button::Style::TransparentBorder),
.style(button::Style::TransparentBorder.into()),
)
.style(card::SimpleCardStyle)
.into()

View File

@ -1,14 +1,14 @@
use std::convert::From;
use iced::{
pure::{column, container, row, widget},
widget::{self, Column, Container},
Length,
};
use crate::{
app::error::Error,
daemon::{client::error::RpcErrorCode, DaemonError},
ui::{color, component::text::*, icon},
ui::component::notification,
};
/// Simple warning message displayed to non technical user.
@ -47,52 +47,8 @@ impl std::fmt::Display for WarningMessage {
pub fn warn<'a, T: 'a>(error: Option<&Error>) -> widget::Container<'a, T> {
if let Some(w) = error {
let message: WarningMessage = w.into();
warning(&message.to_string(), &w.to_string()).width(Length::Fill)
notification::warning(message.to_string(), w.to_string()).width(Length::Fill)
} else {
container(column()).width(Length::Fill)
}
}
pub fn warning<'a, T: 'a>(message: &str, error: &str) -> widget::Container<'a, T> {
container(
widget::Tooltip::new(
row()
.push(icon::warning_icon())
.push(text(message))
.spacing(20),
error,
widget::tooltip::Position::Bottom,
)
.style(TooltipWarningStyle),
)
.padding(15)
.center_x()
.style(WarningStyle)
.width(Length::Fill)
}
struct WarningStyle;
impl widget::container::StyleSheet for WarningStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
border_radius: 0.0,
text_color: iced::Color::BLACK.into(),
background: color::WARNING.into(),
border_color: color::WARNING,
..widget::container::Style::default()
}
}
}
struct TooltipWarningStyle;
impl widget::container::StyleSheet for TooltipWarningStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
border_radius: 0.0,
border_width: 1.0,
text_color: color::WARNING.into(),
background: color::FOREGROUND.into(),
border_color: color::WARNING,
}
Container::new(Column::new()).width(Length::Fill)
}
}

View File

@ -3,8 +3,7 @@ mod message;
mod step;
mod view;
use iced::pure::Element;
use iced::{clipboard, Command, Subscription};
use iced::{clipboard, Command, Element, Subscription};
use iced_native::{window, Event};
use liana::miniscript::bitcoin;

View File

@ -1,6 +1,6 @@
use std::str::FromStr;
use iced::{pure::Element, Command};
use iced::{Command, Element};
use liana::{
descriptors::MultipathDescriptor,
miniscript::{
@ -424,7 +424,7 @@ impl Step for RegisterDescriptor {
fn view(&self) -> Element<Message> {
let desc = self.descriptor.as_ref().unwrap();
view::register_descriptor(
&desc.to_string(),
desc.to_string(),
&self.hws,
self.error.as_ref(),
self.processing,

View File

@ -6,7 +6,7 @@ use std::str::FromStr;
use std::time::Duration;
use async_hwi::DeviceKind;
use iced::{pure::Element, Command};
use iced::{Command, Element};
use liana::{
config::{BitcoinConfig, BitcoindConfig},
descriptors::MultipathDescriptor,

View File

@ -1,5 +1,5 @@
use iced::pure::{column, container, pick_list, row, scrollable, widget, Element};
use iced::{Alignment, Length};
use iced::widget::{Button, Column, Container, PickList, Row, Scrollable};
use iced::{Alignment, Element, Length};
use liana::miniscript::bitcoin;
@ -12,7 +12,7 @@ use crate::{
ui::{
color,
component::{
button, card, form,
button, card, container, form,
text::{text, Text},
},
icon,
@ -28,19 +28,19 @@ const NETWORKS: [bitcoin::Network; 4] = [
];
pub fn welcome(network: &bitcoin::Network, valid: bool) -> Element<Message> {
container(container(
column()
.push(container(
pick_list(&NETWORKS[..], Some(*network), message::Message::Network).padding(10),
Container::new(Container::new(
Column::new()
.push(Container::new(
PickList::new(&NETWORKS[..], Some(*network), message::Message::Network).padding(10),
))
.push(if valid {
container(
Container::new(
button::primary(None, "Start the install")
.on_press(Message::Next)
.width(Length::Units(200)),
)
} else {
card::warning("A data directory already exists for this network")
card::warning("A data directory already exists for this network".to_string())
})
.width(Length::Fill)
.height(Length::Fill)
@ -63,7 +63,7 @@ pub fn define_descriptor<'a>(
sequence: &form::Value<String>,
error: Option<&String>,
) -> Element<'a, Message> {
let col_descriptor = column()
let col_descriptor = Column::new()
.push(text("Descriptor:").bold())
.push(
form::Form::new("Descriptor", imported_descriptor, |msg| {
@ -75,10 +75,10 @@ pub fn define_descriptor<'a>(
)
.spacing(10);
let col_user_xpub = column()
let col_user_xpub = Column::new()
.push(text("Your xpub:").bold())
.push(
row()
Row::new()
.push(
form::Form::new("Xpub", user_xpub, |msg| {
Message::DefineDescriptor(message::DefineDescriptor::UserXpubEdited(msg))
@ -99,10 +99,10 @@ pub fn define_descriptor<'a>(
)
.spacing(10);
let col_heir_xpub = column()
let col_heir_xpub = Column::new()
.push(text("Heir xpub:").bold())
.push(
row()
Row::new()
.push(
form::Form::new("Xpub", heir_xpub, |msg| {
Message::DefineDescriptor(message::DefineDescriptor::HeirXpubEdited(msg))
@ -123,10 +123,10 @@ pub fn define_descriptor<'a>(
)
.spacing(10);
let col_sequence = column()
let col_sequence = Column::new()
.push(text("Number of block:").bold())
.push(
container(
Container::new(
form::Form::new("Number of block", sequence, |msg| {
Message::DefineDescriptor(message::DefineDescriptor::SequenceEdited(msg))
})
@ -139,10 +139,10 @@ pub fn define_descriptor<'a>(
.spacing(10);
layout(
column()
Column::new()
.push(text("Create the descriptor").bold().size(50))
.push(
column()
Column::new()
.push(col_user_xpub)
.push(col_sequence)
.push(col_heir_xpub)
@ -163,7 +163,7 @@ pub fn define_descriptor<'a>(
.on_press(Message::Next)
},
)
.push_maybe(error.map(|e| card::error("Failed to create descriptor", e)))
.push_maybe(error.map(|e| card::error("Failed to create descriptor", e.to_string())))
.width(Length::Fill)
.height(Length::Fill)
.padding(100)
@ -173,34 +173,34 @@ pub fn define_descriptor<'a>(
}
pub fn register_descriptor<'a>(
descriptor: &str,
descriptor: String,
hws: &[(HardwareWallet, Option<[u8; 32]>)],
error: Option<&Error>,
processing: bool,
chosen_hw: Option<usize>,
) -> Element<'a, Message> {
layout(
column()
Column::new()
.push(text("Register descriptor").bold().size(50))
.push(
column()
.push(text(descriptor).small())
Column::new()
.push(text(descriptor.clone()).small())
.push(
button::transparent_border(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clibpboard(descriptor.to_string())),
.on_press(Message::Clibpboard(descriptor)),
)
.spacing(10)
.align_items(Alignment::Center),
)
.push_maybe(error.map(|e| card::error("Failed to import xpub", &e.to_string())))
.push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string())))
.push(if !hws.is_empty() {
column()
.push(text(&format!("{} hardware wallets connected", hws.len())).bold())
Column::new()
.push(text(format!("{} hardware wallets connected", hws.len())).bold())
.spacing(10)
.push(
hws.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, hw)| {
.fold(Column::new().spacing(10), |col, (i, hw)| {
col.push(hw_list_view(
i,
&hw.0,
@ -212,8 +212,8 @@ pub fn register_descriptor<'a>(
)
.width(Length::Fill)
} else {
column().push(card::simple(
column()
Column::new().push(card::simple(
Column::new()
.spacing(20)
.push("No hardware wallet connected")
.push(button::primary(None, "Refresh").on_press(Message::Reload))
@ -238,7 +238,7 @@ pub fn define_bitcoin<'a>(
address: &form::Value<String>,
cookie_path: &form::Value<String>,
) -> Element<'a, Message> {
let col_address = column()
let col_address = Column::new()
.push(text("Address:").bold())
.push(
form::Form::new("Address", address, |msg| {
@ -250,7 +250,7 @@ pub fn define_bitcoin<'a>(
)
.spacing(10);
let col_cookie = column()
let col_cookie = Column::new()
.push(text("Cookie path:").bold())
.push(
form::Form::new("Cookie path", cookie_path, |msg| {
@ -263,7 +263,7 @@ pub fn define_bitcoin<'a>(
.spacing(10);
layout(
column()
Column::new()
.push(
text("Set up connection to the Bitcoin full node")
.bold()
@ -287,9 +287,9 @@ pub fn define_bitcoin<'a>(
pub fn install<'a>(
generating: bool,
config_path: Option<&std::path::PathBuf>,
warning: Option<&String>,
warning: Option<&'a String>,
) -> Element<'a, Message> {
let mut col = column()
let mut col = Column::new()
.width(Length::Fill)
.height(Length::Fill)
.padding(100)
@ -304,10 +304,10 @@ pub fn install<'a>(
col = col.push(button::primary(None, "Installing ...").width(Length::Units(200)))
} else if let Some(path) = config_path {
col = col.push(
container(
column()
.push(container(text("Installed !")))
.push(container(
Container::new(
Column::new()
.push(Container::new(text("Installed !")))
.push(Container::new(
button::primary(None, "Start")
.on_press(Message::Exit(path.clone()))
.width(Length::Units(200)),
@ -338,7 +338,7 @@ pub fn hardware_wallet_xpubs_modal<'a>(
chosen_hw: Option<usize>,
) -> Element<'a, Message> {
modal(
column()
Column::new()
.push(
text(if is_heir {
"Import the Heir xpub"
@ -348,15 +348,15 @@ pub fn hardware_wallet_xpubs_modal<'a>(
.bold()
.size(50),
)
.push_maybe(error.map(|e| card::error("Failed to import xpub", &e.to_string())))
.push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string())))
.push(if !hws.is_empty() {
column()
.push(text(&format!("{} hardware wallets connected", hws.len())).bold())
Column::new()
.push(text(format!("{} hardware wallets connected", hws.len())).bold())
.spacing(10)
.push(
hws.iter()
.enumerate()
.fold(column().spacing(10), |col, (i, hw)| {
.fold(Column::new().spacing(10), |col, (i, hw)| {
col.push(hw_list_view(
i,
hw,
@ -368,10 +368,10 @@ pub fn hardware_wallet_xpubs_modal<'a>(
)
.width(Length::Fill)
} else {
column()
Column::new()
.push(
card::simple(
column()
Column::new()
.spacing(20)
.width(Length::Fill)
.push("Please connect a hardware wallet")
@ -397,18 +397,18 @@ fn hw_list_view<'a>(
processing: bool,
registered: bool,
) -> Element<'a, Message> {
let mut bttn = iced::pure::button(
row()
let mut bttn = Button::new(
Row::new()
.push(
column()
.push(text(&format!("{}", hw.kind)).bold())
.push(text(&format!("fingerprint: {}", hw.fingerprint)).small())
Column::new()
.push(text(format!("{}", hw.kind)).bold())
.push(text(format!("fingerprint: {}", hw.fingerprint)).small())
.spacing(5)
.width(Length::Fill),
)
.push_maybe(if chosen && processing {
Some(
column()
Column::new()
.push(text("Processing..."))
.push(text("Please check your device").small()),
)
@ -416,7 +416,7 @@ fn hw_list_view<'a>(
None
})
.push_maybe(if registered {
Some(column().push(icon::circle_check_icon().color(color::SUCCESS)))
Some(Column::new().push(icon::circle_check_icon().style(color::SUCCESS)))
} else {
None
})
@ -424,59 +424,49 @@ fn hw_list_view<'a>(
.width(Length::Fill),
)
.padding(10)
.style(button::Style::TransparentBorder)
.style(button::Style::TransparentBorder.into())
.width(Length::Fill);
if !processing {
bttn = bttn.on_press(Message::Select(i));
}
container(bttn)
Container::new(bttn)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.into()
}
fn layout<'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
container(scrollable(
column()
Container::new(Scrollable::new(
Column::new()
.push(
container(button::transparent(None, "< Previous").on_press(Message::Previous))
Container::new(button::transparent(None, "< Previous").on_press(Message::Previous))
.padding(5),
)
.push(container(content).width(Length::Fill).center_x()),
.push(Container::new(content).width(Length::Fill).center_x()),
))
.center_x()
.height(Length::Fill)
.width(Length::Fill)
.style(BackgroundStyle)
.style(container::Style::Background)
.into()
}
fn modal<'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
container(scrollable(
column()
Container::new(Scrollable::new(
Column::new()
.push(
row().push(column().width(Length::Fill)).push(
container(
Row::new().push(Column::new().width(Length::Fill)).push(
Container::new(
button::primary(Some(icon::cross_icon()), "Close").on_press(Message::Close),
)
.padding(10),
),
)
.push(container(content).width(Length::Fill).center_x()),
.push(Container::new(content).width(Length::Fill).center_x()),
))
.center_x()
.height(Length::Fill)
.width(Length::Fill)
.style(BackgroundStyle)
.style(container::Style::Background)
.into()
}
pub struct BackgroundStyle;
impl widget::container::StyleSheet for BackgroundStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
background: color::BACKGROUND.into(),
..widget::container::Style::default()
}
}
}

View File

@ -3,7 +3,10 @@ use std::io::ErrorKind;
use std::path::PathBuf;
use std::sync::Arc;
use iced::pure::{column, text, Element};
use iced::{
widget::{Column, Text},
Element,
};
use iced::{Alignment, Command, Subscription};
use iced_native::{window, Event};
use log::{debug, info};
@ -199,16 +202,16 @@ impl Loader {
pub fn view(&self) -> Element<Message> {
match &self.step {
Step::StartingDaemon => cover(text("Starting daemon...")),
Step::Connecting => cover(text("Connecting to daemon...")),
Step::Syncing { progress, .. } => cover(text(&format!("Syncing... {}%", progress))),
Step::Error(error) => cover(text(&format!("Error: {}", error))),
Step::StartingDaemon => cover(Text::new("Starting daemon...")),
Step::Connecting => cover(Text::new("Connecting to daemon...")),
Step::Syncing { progress, .. } => cover(Text::new(format!("Syncing... {}%", progress))),
Step::Error(error) => cover(Text::new(format!("Error: {}", error))),
}
}
}
pub fn cover<'a, T: 'a, C: Into<Element<'a, T>>>(content: C) -> Element<'a, T> {
column()
Column::new()
.push(content)
.width(iced::Length::Fill)
.height(iced::Length::Fill)

View File

@ -1,7 +1,6 @@
use std::{error::Error, path::PathBuf, str::FromStr};
use iced::pure::{Application, Element};
use iced::{executor, Command, Settings, Subscription};
use iced::{executor, Application, Command, Element, Settings, Subscription};
extern crate serde;
extern crate serde_json;
@ -94,6 +93,7 @@ impl Application for GUI {
type Executor = executor::Default;
type Message = Message;
type Flags = Config;
type Theme = iced::Theme;
fn title(&self) -> String {
match self.state {

View File

@ -1,6 +1,6 @@
use iced::{
pure::{container, widget, Element},
Length,
widget::{self, Container},
Element, Length,
};
use crate::ui::{color, icon};
@ -12,36 +12,49 @@ pub enum Style {
}
impl widget::container::StyleSheet for Style {
fn style(&self) -> widget::container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
match self {
Self::Standard => widget::container::Style {
Self::Standard => widget::container::Appearance {
border_radius: 40.0,
background: color::BACKGROUND.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
Self::Success => widget::container::Style {
Self::Success => widget::container::Appearance {
border_radius: 40.0,
background: color::SUCCESS_LIGHT.into(),
text_color: color::SUCCESS.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
Self::Warning => widget::container::Style {
Self::Warning => widget::container::Appearance {
border_radius: 40.0,
background: color::WARNING_LIGHT.into(),
text_color: color::WARNING.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
}
}
}
pub struct Badge<S: widget::container::StyleSheet> {
icon: widget::Text,
style: S,
impl From<Style> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: Style) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl Badge<Style> {
pub fn new(icon: widget::Text) -> Self {
impl From<Style> for iced::theme::Container {
fn from(i: Style) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}
pub struct Badge {
icon: widget::Text<'static>,
style: Style,
}
impl Badge {
pub fn new(icon: widget::Text<'static>) -> Self {
Self {
icon,
style: Style::Standard,
@ -55,11 +68,9 @@ impl Badge<Style> {
}
}
impl<'a, Message: 'a, S: 'a + widget::container::StyleSheet> From<Badge<S>>
for Element<'a, Message>
{
fn from(badge: Badge<S>) -> Element<'a, Message> {
container(badge.icon.width(Length::Units(20)))
impl<'a, Message: 'a> From<Badge> for Element<'a, Message> {
fn from(badge: Badge) -> Element<'a, Message> {
Container::new(badge.icon.width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(badge.style)
@ -69,51 +80,29 @@ impl<'a, Message: 'a, S: 'a + widget::container::StyleSheet> From<Badge<S>>
}
}
pub struct ReceiveStyle;
impl widget::container::StyleSheet for ReceiveStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
border_radius: 40.0,
background: color::BACKGROUND.into(),
..widget::container::Style::default()
}
}
}
pub fn receive<T>() -> widget::container::Container<'static, T> {
container(icon::receive_icon().width(Length::Units(20)))
Container::new(icon::receive_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(ReceiveStyle)
.style(Style::Standard)
.center_x()
.center_y()
}
pub struct SpendStyle;
impl widget::container::StyleSheet for SpendStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
border_radius: 40.0,
background: color::BACKGROUND.into(),
..widget::container::Style::default()
}
}
}
pub fn spend<T>() -> widget::container::Container<'static, T> {
container(icon::send_icon().width(Length::Units(20)))
Container::new(icon::send_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(ReceiveStyle)
.style(Style::Standard)
.center_x()
.center_y()
}
pub fn coin<T>() -> widget::container::Container<'static, T> {
container(icon::coin_icon().width(Length::Units(20)))
Container::new(icon::coin_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(ReceiveStyle)
.style(Style::Standard)
.center_x()
.center_y()
}
@ -126,32 +115,45 @@ pub enum PillStyle {
}
impl widget::container::StyleSheet for PillStyle {
fn style(&self) -> widget::container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
match self {
Self::Primary => widget::container::Style {
Self::Primary => widget::container::Appearance {
background: color::PRIMARY.into(),
border_radius: 10.0,
text_color: iced::Color::WHITE.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
Self::InversePrimary => widget::container::Style {
Self::InversePrimary => widget::container::Appearance {
background: color::FOREGROUND.into(),
border_radius: 10.0,
text_color: color::PRIMARY.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
Self::Success => widget::container::Style {
Self::Success => widget::container::Appearance {
background: color::SUCCESS.into(),
border_radius: 10.0,
text_color: iced::Color::WHITE.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
Self::Simple => widget::container::Style {
Self::Simple => widget::container::Appearance {
background: color::BACKGROUND.into(),
border_radius: 10.0,
text_color: iced::Color::BLACK.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
},
}
}
}
impl From<PillStyle> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: PillStyle) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<PillStyle> for iced::theme::Container {
fn from(i: PillStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}

View File

@ -1,33 +1,33 @@
use iced::pure::{
container, row,
widget::{button, Container},
};
use iced::widget::{button, container, Container, Row, Text};
use iced::{Alignment, Color, Length, Vector};
use super::text::text;
use crate::ui::color;
pub fn alert<'a, T: 'a>(icon: Option<iced::Text>, t: &str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Destructive)
pub fn alert<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Destructive.into())
}
pub fn primary<'a, T: 'a>(icon: Option<iced::Text>, t: &str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Primary)
pub fn primary<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Primary.into())
}
pub fn transparent<'a, T: 'a>(icon: Option<iced::Text>, t: &str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Transparent)
pub fn transparent<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Transparent.into())
}
pub fn transparent_border<'a, T: 'a>(icon: Option<iced::Text>, t: &str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::TransparentBorder)
pub fn transparent_border<'a, T: 'a>(
icon: Option<Text<'a>>,
t: &'static str,
) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::TransparentBorder.into())
}
fn content<'a, T: 'a>(icon: Option<iced::Text>, t: &str) -> Container<'a, T> {
fn content<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Container<'a, T> {
match icon {
None => container(text(t)).width(Length::Fill).center_x().padding(5),
Some(i) => container(
row()
Row::new()
.push(i)
.push(text(t))
.spacing(10)
@ -50,9 +50,10 @@ pub enum Style {
}
impl button::StyleSheet for Style {
fn active(&self) -> button::Style {
type Style = iced::Theme;
fn active(&self, _style: &Self::Style) -> button::Appearance {
match self {
Style::Primary => button::Style {
Style::Primary => button::Appearance {
shadow_offset: Vector::default(),
background: color::PRIMARY.into(),
border_radius: 10.0,
@ -60,7 +61,7 @@ impl button::StyleSheet for Style {
border_color: Color::TRANSPARENT,
text_color: color::FOREGROUND,
},
Style::Destructive => button::Style {
Style::Destructive => button::Appearance {
shadow_offset: Vector::default(),
background: color::FOREGROUND.into(),
border_radius: 10.0,
@ -68,7 +69,7 @@ impl button::StyleSheet for Style {
border_color: color::ALERT,
text_color: color::ALERT,
},
Style::Transparent | Style::TransparentBorder => button::Style {
Style::Transparent | Style::TransparentBorder => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
@ -76,7 +77,7 @@ impl button::StyleSheet for Style {
border_color: Color::TRANSPARENT,
text_color: Color::BLACK,
},
Style::Border => button::Style {
Style::Border => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
@ -87,9 +88,9 @@ impl button::StyleSheet for Style {
}
}
fn hovered(&self) -> button::Style {
fn hovered(&self, _style: &Self::Style) -> button::Appearance {
match self {
Style::Primary => button::Style {
Style::Primary => button::Appearance {
shadow_offset: Vector::default(),
background: color::PRIMARY.into(),
border_radius: 10.0,
@ -97,7 +98,7 @@ impl button::StyleSheet for Style {
border_color: Color::TRANSPARENT,
text_color: color::FOREGROUND,
},
Style::Destructive => button::Style {
Style::Destructive => button::Appearance {
shadow_offset: Vector::default(),
background: color::FOREGROUND.into(),
border_radius: 10.0,
@ -105,7 +106,7 @@ impl button::StyleSheet for Style {
border_color: color::ALERT,
text_color: color::ALERT,
},
Style::Transparent => button::Style {
Style::Transparent => button::Appearance {
shadow_offset: Vector::default(),
background: color::BACKGROUND.into(),
border_radius: 10.0,
@ -113,7 +114,7 @@ impl button::StyleSheet for Style {
border_color: Color::TRANSPARENT,
text_color: Color::BLACK,
},
Style::TransparentBorder => button::Style {
Style::TransparentBorder => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
@ -121,7 +122,7 @@ impl button::StyleSheet for Style {
border_color: Color::BLACK,
text_color: Color::BLACK,
},
Style::Border => button::Style {
Style::Border => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
@ -132,3 +133,15 @@ impl button::StyleSheet for Style {
}
}
}
impl From<Style> for Box<dyn button::StyleSheet<Style = iced::Theme>> {
fn from(s: Style) -> Box<dyn button::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<Style> for iced::theme::Button {
fn from(i: Style) -> iced::theme::Button {
iced::theme::Button::Custom(i.into())
}
}

View File

@ -1,30 +1,46 @@
use iced::pure::{container, row, tooltip, widget, Element};
use iced::{
widget::{self, Container, Row, Tooltip},
Element,
};
use crate::ui::{color, component::text::text, icon};
pub fn simple<'a, T: 'a, C: Into<Element<'a, T>>>(content: C) -> widget::Container<'a, T> {
container(content).padding(15).style(SimpleCardStyle)
Container::new(content).padding(15).style(SimpleCardStyle)
}
pub struct SimpleCardStyle;
impl widget::container::StyleSheet for SimpleCardStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
widget::container::Appearance {
border_radius: 10.0,
background: color::FOREGROUND.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
}
}
}
impl From<SimpleCardStyle> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: SimpleCardStyle) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<SimpleCardStyle> for iced::theme::Container {
fn from(i: SimpleCardStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}
/// display an error card with the message and the error in a tooltip.
pub fn warning<'a, T: 'a>(message: &str) -> widget::Container<'a, T> {
container(
row()
pub fn warning<'a, T: 'a>(message: String) -> widget::Container<'a, T> {
Container::new(
Row::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.push(icon::warning_octagon_icon().color(color::WARNING))
.push(text(message).color(color::WARNING)),
.push(icon::warning_octagon_icon().style(color::WARNING))
.push(text(message).style(color::WARNING)),
)
.padding(15)
.style(WarningCardStyle)
@ -32,26 +48,39 @@ pub fn warning<'a, T: 'a>(message: &str) -> widget::Container<'a, T> {
pub struct WarningCardStyle;
impl widget::container::StyleSheet for WarningCardStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
widget::container::Appearance {
border_radius: 10.0,
border_color: color::WARNING,
border_width: 1.5,
background: color::FOREGROUND.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
}
}
}
impl From<WarningCardStyle> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: WarningCardStyle) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<WarningCardStyle> for iced::theme::Container {
fn from(i: WarningCardStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}
/// display an error card with the message and the error in a tooltip.
pub fn error<'a, T: 'a>(message: &str, error: &str) -> widget::Container<'a, T> {
container(
tooltip(
row()
pub fn error<'a, T: 'a>(message: &'static str, error: String) -> widget::Container<'a, T> {
Container::new(
Tooltip::new(
Row::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.push(icon::block_icon().color(color::ALERT))
.push(text(message).color(color::ALERT)),
.push(icon::block_icon().style(color::ALERT))
.push(text(message).style(color::ALERT)),
error,
widget::tooltip::Position::Bottom,
)
@ -63,13 +92,26 @@ pub fn error<'a, T: 'a>(message: &str, error: &str) -> widget::Container<'a, T>
pub struct ErrorCardStyle;
impl widget::container::StyleSheet for ErrorCardStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
widget::container::Appearance {
border_radius: 10.0,
border_color: color::ALERT,
border_width: 1.5,
background: color::FOREGROUND.into(),
..widget::container::Style::default()
..widget::container::Appearance::default()
}
}
}
impl From<ErrorCardStyle> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: ErrorCardStyle) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<ErrorCardStyle> for iced::theme::Container {
fn from(i: ErrorCardStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}

View File

@ -1,95 +0,0 @@
use iced::pure::{button, column};
use iced_lazy::pure::{self, Component};
use iced_native::text;
use iced_pure::Element;
use std::marker::PhantomData;
use crate::ui::component::button::Style;
pub fn collapse<
'a,
Message: 'a,
T: Into<Message> + 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<Element<'a, Message, Renderer>> {
Collapse {
header,
content,
phantom: PhantomData,
}
}
struct Collapse<'a, H, C> {
header: H,
content: C,
phantom: PhantomData<&'a H>,
}
#[derive(Debug, Clone, Copy)]
enum Event<T> {
Internal(T),
Collapse(bool),
}
impl<'a, Message, Renderer, T, H, C> Component<Message, Renderer> for Collapse<'a, H, C>
where
T: Into<Message> + Clone + 'a,
H: Fn() -> Element<'a, T, Renderer>,
C: Fn() -> Element<'a, T, Renderer>,
Renderer: text::Renderer + 'static,
{
type State = bool;
type Event = Event<T>;
fn update(&mut self, state: &mut Self::State, event: Event<T>) -> Option<Message> {
match event {
Event::Internal(e) => Some(e.into()),
Event::Collapse(s) => {
*state = s;
None
}
}
}
fn view(&self, state: &Self::State) -> Element<Self::Event, Renderer> {
if *state {
column()
.push(
button((self.header)().map(Event::Internal))
.style(Style::TransparentBorder)
.padding(10)
.on_press(Event::Collapse(false)),
)
.push((self.content)().map(Event::Internal))
.into()
} else {
column()
.push(
button((self.header)().map(Event::Internal))
.padding(10)
.style(Style::TransparentBorder)
.on_press(Event::Collapse(true)),
)
.into()
}
}
}
impl<'a, Message, Renderer, T, H: 'a, C: 'a> From<Collapse<'a, H, C>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'static + text::Renderer,
T: Into<Message> + 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)
}
}

View File

@ -0,0 +1,37 @@
use crate::ui::color;
use iced::widget::container;
pub enum Style {
Sidebar,
Background,
}
impl container::StyleSheet for Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> container::Appearance {
match self {
Self::Background => container::Appearance {
background: color::BACKGROUND.into(),
..container::Appearance::default()
},
Self::Sidebar => container::Appearance {
background: color::FOREGROUND.into(),
border_width: 1.0,
border_color: color::SECONDARY,
..container::Appearance::default()
},
}
}
}
impl From<Style> for Box<dyn container::StyleSheet<Style = iced::Theme>> {
fn from(s: Style) -> Box<dyn container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<Style> for iced::theme::Container {
fn from(i: Style) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}

View File

@ -1,9 +1,10 @@
use iced::pure::{
column, container,
widget::text_input::{Style, StyleSheet, TextInput},
Element,
use iced::{
widget::{
text_input::{Appearance, StyleSheet, TextInput},
Column, Container,
},
Element, Length,
};
use iced::Length;
use crate::ui::{color, component::text::*, util::Collection};
@ -70,8 +71,8 @@ where
impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
fn from(form: Form<'a, Message>) -> Element<'a, Message> {
container(
column()
Container::new(
Column::new()
.push(if !form.valid {
form.input.style(InvalidFormStyle)
} else {
@ -79,7 +80,7 @@ impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
})
.push_maybe(if !form.valid {
form.warning
.map(|message| text(message).color(color::ALERT).small())
.map(|message| text(message).style(color::ALERT).small())
} else {
None
})
@ -93,8 +94,9 @@ impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
struct InvalidFormStyle;
impl StyleSheet for InvalidFormStyle {
fn active(&self) -> Style {
Style {
type Style = iced::Theme;
fn active(&self, _style: &Self::Style) -> Appearance {
Appearance {
background: iced::Background::Color(color::FOREGROUND),
border_radius: 5.0,
border_width: 1.0,
@ -102,22 +104,34 @@ impl StyleSheet for InvalidFormStyle {
}
}
fn focused(&self) -> Style {
Style {
fn focused(&self, style: &Self::Style) -> Appearance {
Appearance {
border_color: color::ALERT,
..self.active()
..self.active(style)
}
}
fn placeholder_color(&self) -> iced::Color {
fn placeholder_color(&self, _style: &Self::Style) -> iced::Color {
iced::Color::from_rgb(0.7, 0.7, 0.7)
}
fn value_color(&self) -> iced::Color {
fn value_color(&self, _style: &Self::Style) -> iced::Color {
iced::Color::from_rgb(0.3, 0.3, 0.3)
}
fn selection_color(&self) -> iced::Color {
fn selection_color(&self, _style: &Self::Style) -> iced::Color {
iced::Color::from_rgb(0.8, 0.8, 1.0)
}
}
impl From<InvalidFormStyle> for Box<dyn StyleSheet<Style = iced::Theme>> {
fn from(s: InvalidFormStyle) -> Box<dyn StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<InvalidFormStyle> for iced::theme::TextInput {
fn from(i: InvalidFormStyle) -> iced::theme::TextInput {
iced::theme::TextInput::Custom(i.into())
}
}

View File

@ -1,27 +1,41 @@
pub mod badge;
pub mod button;
pub mod card;
pub mod collapse;
pub mod container;
pub mod form;
pub mod notification;
pub mod text;
use iced::pure::widget::{container, Column, Container};
use iced::widget::{Column, Container, Text};
use iced::Length;
use crate::ui::color;
pub fn separation<'a, T: 'a>() -> Container<'a, T> {
Container::new(Column::new().push(iced::Text::new(" ")))
Container::new(Column::new().push(Text::new(" ")))
.style(SepStyle)
.height(Length::Units(1))
}
pub struct SepStyle;
impl container::StyleSheet for SepStyle {
fn style(&self) -> container::Style {
container::Style {
impl iced::widget::container::StyleSheet for SepStyle {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> iced::widget::container::Appearance {
iced::widget::container::Appearance {
background: color::SECONDARY.into(),
..container::Style::default()
..iced::widget::container::Appearance::default()
}
}
}
impl From<SepStyle> for Box<dyn iced::widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: SepStyle) -> Box<dyn iced::widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<SepStyle> for iced::theme::Container {
fn from(i: SepStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}

View File

@ -1,7 +1,10 @@
use crate::{color, icon};
use iced::{container, tooltip, Container, Length, Row, Text, Tooltip};
use crate::ui::{color, icon};
use iced::{
widget::{container, tooltip, Container, Row, Text, Tooltip},
Length,
};
pub fn warning<'a, T: 'a>(message: &str, error: &str) -> Container<'a, T> {
pub fn warning<'a, T: 'a>(message: String, error: String) -> Container<'a, T> {
Container::new(Container::new(
Tooltip::new(
Row::new()
@ -19,23 +22,37 @@ pub fn warning<'a, T: 'a>(message: &str, error: &str) -> Container<'a, T> {
.width(Length::Fill)
}
struct WarningStyle;
pub struct WarningStyle;
impl container::StyleSheet for WarningStyle {
fn style(&self) -> container::Style {
container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> container::Appearance {
container::Appearance {
border_radius: 0.0,
text_color: iced::Color::BLACK.into(),
background: color::WARNING.into(),
border_color: color::WARNING,
..container::Style::default()
..container::Appearance::default()
}
}
}
struct TooltipWarningStyle;
impl From<WarningStyle> for Box<dyn container::StyleSheet<Style = iced::Theme>> {
fn from(s: WarningStyle) -> Box<dyn container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<WarningStyle> for iced::theme::Container {
fn from(i: WarningStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}
pub struct TooltipWarningStyle;
impl container::StyleSheet for TooltipWarningStyle {
fn style(&self) -> container::Style {
container::Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> container::Appearance {
container::Appearance {
border_radius: 0.0,
border_width: 1.0,
text_color: color::WARNING.into(),
@ -44,3 +61,15 @@ impl container::StyleSheet for TooltipWarningStyle {
}
}
}
impl From<TooltipWarningStyle> for Box<dyn container::StyleSheet<Style = iced::Theme>> {
fn from(s: TooltipWarningStyle) -> Box<dyn container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<TooltipWarningStyle> for iced::theme::Container {
fn from(i: TooltipWarningStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}

View File

@ -1,7 +1,8 @@
use crate::ui::font;
use std::borrow::Cow;
pub fn text(content: &str) -> iced::pure::widget::Text {
iced::pure::widget::Text::new(content)
pub fn text<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(25)
}
@ -11,7 +12,7 @@ pub trait Text {
fn small(self) -> Self;
}
impl Text for iced::pure::widget::Text {
impl Text for iced::widget::Text<'_> {
fn bold(self) -> Self {
self.font(font::BOLD)
}

View File

@ -1,181 +1,180 @@
use iced::pure::text;
use iced::{alignment, Font, Length, Text};
use iced::{alignment, widget::Text, Font, Length};
const ICONS: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../../static/icons/bootstrap-icons.ttf"),
};
fn icon(unicode: char) -> Text {
text(&unicode.to_string())
fn icon(unicode: char) -> Text<'static> {
Text::new(unicode.to_string())
.font(ICONS)
.width(Length::Units(20))
.horizontal_alignment(alignment::Horizontal::Center)
.size(20)
}
pub fn hourglass_icon() -> Text {
pub fn hourglass_icon() -> Text<'static> {
icon('\u{F41F}')
}
pub fn hourglass_done_icon() -> Text {
pub fn hourglass_done_icon() -> Text<'static> {
icon('\u{F41E}')
}
pub fn vault_icon() -> Text {
pub fn vault_icon() -> Text<'static> {
icon('\u{F65A}')
}
pub fn bitcoin_icon() -> Text {
pub fn bitcoin_icon() -> Text<'static> {
icon('\u{F635}')
}
pub fn history_icon() -> Text {
pub fn history_icon() -> Text<'static> {
icon('\u{F292}')
}
pub fn home_icon() -> Text {
pub fn home_icon() -> Text<'static> {
icon('\u{F3FC}')
}
pub fn unlock_icon() -> Text {
pub fn unlock_icon() -> Text<'static> {
icon('\u{F600}')
}
pub fn warning_octagon_icon() -> Text {
pub fn warning_octagon_icon() -> Text<'static> {
icon('\u{F337}')
}
pub fn send_icon() -> Text {
pub fn send_icon() -> Text<'static> {
icon('\u{F144}')
}
pub fn connect_device_icon() -> Text {
pub fn connect_device_icon() -> Text<'static> {
icon('\u{F348}')
}
pub fn connected_device_icon() -> Text {
pub fn connected_device_icon() -> Text<'static> {
icon('\u{F350}')
}
pub fn receive_icon() -> Text {
pub fn receive_icon() -> Text<'static> {
icon('\u{F123}')
}
pub fn calendar_icon() -> Text {
pub fn calendar_icon() -> Text<'static> {
icon('\u{F1E8}')
}
pub fn turnback_icon() -> Text {
pub fn turnback_icon() -> Text<'static> {
icon('\u{F131}')
}
pub fn vaults_icon() -> Text {
pub fn vaults_icon() -> Text<'static> {
icon('\u{F1C7}')
}
pub fn coin_icon() -> Text {
pub fn coin_icon() -> Text<'static> {
icon('\u{F567}')
}
pub fn settings_icon() -> Text {
pub fn settings_icon() -> Text<'static> {
icon('\u{F3E5}')
}
pub fn block_icon() -> Text {
pub fn block_icon() -> Text<'static> {
icon('\u{F1C8}')
}
pub fn square_icon() -> Text {
pub fn square_icon() -> Text<'static> {
icon('\u{F584}')
}
pub fn square_check_icon() -> Text {
pub fn square_check_icon() -> Text<'static> {
icon('\u{F26D}')
}
pub fn circle_check_icon() -> Text {
pub fn circle_check_icon() -> Text<'static> {
icon('\u{F26B}')
}
pub fn network_icon() -> Text {
pub fn network_icon() -> Text<'static> {
icon('\u{F40D}')
}
pub fn dot_icon() -> Text {
pub fn dot_icon() -> Text<'static> {
icon('\u{F287}')
}
pub fn clipboard_icon() -> Text {
pub fn clipboard_icon() -> Text<'static> {
icon('\u{F28E}')
}
pub fn shield_icon() -> Text {
pub fn shield_icon() -> Text<'static> {
icon('\u{F53F}')
}
pub fn shield_notif_icon() -> Text {
pub fn shield_notif_icon() -> Text<'static> {
icon('\u{F530}')
}
pub fn shield_check_icon() -> Text {
pub fn shield_check_icon() -> Text<'static> {
icon('\u{F52F}')
}
pub fn person_check_icon() -> Text {
pub fn person_check_icon() -> Text<'static> {
icon('\u{F4D6}')
}
pub fn person_icon() -> Text {
pub fn person_icon() -> Text<'static> {
icon('\u{F4DA}')
}
pub fn tooltip_icon() -> Text {
pub fn tooltip_icon() -> Text<'static> {
icon('\u{F431}')
}
pub fn plus_icon() -> Text {
pub fn plus_icon() -> Text<'static> {
icon('\u{F4FE}')
}
pub fn warning_icon() -> Text {
pub fn warning_icon() -> Text<'static> {
icon('\u{F33B}')
}
pub fn chip_icon() -> Text {
pub fn chip_icon() -> Text<'static> {
icon('\u{F2D6}')
}
pub fn trash_icon() -> Text {
pub fn trash_icon() -> Text<'static> {
icon('\u{F5DE}')
}
pub fn key_icon() -> Text {
pub fn key_icon() -> Text<'static> {
icon('\u{F44F}')
}
pub fn cross_icon() -> Text {
pub fn cross_icon() -> Text<'static> {
icon('\u{F62A}')
}
pub fn pencil_icon() -> Text {
pub fn pencil_icon() -> Text<'static> {
icon('\u{F4CB}')
}
#[allow(dead_code)]
pub fn stakeholder_icon() -> Text {
pub fn stakeholder_icon() -> Text<'static> {
icon('\u{F4AE}')
}
#[allow(dead_code)]
pub fn manager_icon() -> Text {
pub fn manager_icon() -> Text<'static> {
icon('\u{F4B4}')
}
pub fn done_icon() -> Text {
pub fn done_icon() -> Text<'static> {
icon('\u{F26B}')
}
pub fn todo_icon() -> Text {
pub fn todo_icon() -> Text<'static> {
icon('\u{F28A}')
}

View File

@ -1,5 +1,5 @@
/// from hecjr idea on Discord
use iced::pure::{
use iced::{
widget::{Column, Row},
Element,
};