gui: transitioning to ui crate

This commit is contained in:
edouard 2023-03-24 17:53:24 +01:00
parent 8d337eb7e1
commit d4d5a66aa1
69 changed files with 533 additions and 2184 deletions

10
gui/Cargo.lock generated
View File

@ -1690,6 +1690,7 @@ dependencies = [
"iced_lazy",
"iced_native",
"liana",
"liana_ui",
"log",
"serde",
"serde_json",
@ -1699,6 +1700,15 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "liana_ui"
version = "0.1.0"
dependencies = [
"iced",
"iced_lazy",
"iced_native",
]
[[package]]
name = "libc"
version = "0.2.137"

View File

@ -16,6 +16,7 @@ path = "src/main.rs"
[dependencies]
async-hwi = "0.0.4"
liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false }
liana_ui = { path = "ui" }
backtrace = "0.3"
base64 = "0.13"
@ -39,3 +40,6 @@ chrono = "0.4"
[dev-dependencies]
tokio = {version = "1.9.0", features = ["rt", "macros"]}
[workspace]
members = ["ui"]

View File

@ -15,10 +15,11 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use iced::{clipboard, time, Command, Element, Subscription};
use iced::{clipboard, time, Command, Subscription};
use tracing::{info, warn};
pub use liana::{config::Config as DaemonConfig, miniscript::bitcoin};
use liana_ui::widget::Element;
pub use config::Config;
pub use message::Message;

View File

@ -1,7 +1,9 @@
use std::cmp::Ordering;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana_ui::widget::Element;
use crate::{
app::{cache::Cache, error::Error, menu::Menu, message::Message, state::State, view},

View File

@ -8,8 +8,8 @@ use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use iced::{widget::qr_code, Command, Subscription};
use iced::{widget::Column, Element};
use liana::miniscript::bitcoin::{Address, Amount};
use liana_ui::widget::*;
use super::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet};

View File

@ -1,7 +1,9 @@
use std::str::FromStr;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana_ui::{component::form, widget::Element};
use crate::{
app::{
@ -18,7 +20,6 @@ use crate::{
model::{remaining_sequence, Coin, SpendTx},
Daemon,
},
ui::component::form,
};
use liana::miniscript::bitcoin::{Address, Amount};

View File

@ -5,15 +5,16 @@ use std::str::FromStr;
use std::sync::Arc;
use chrono::prelude::*;
use iced::{Command, Element};
use iced::Command;
use tracing::info;
use liana::config::{BitcoinConfig, BitcoindConfig, Config};
use liana_ui::{component::form, widget::Element};
use crate::{
app::{cache::Cache, error::Error, message::Message, state::settings::Setting, view, State},
daemon::Daemon,
ui::component::form,
};
#[derive(Debug)]

View File

@ -5,7 +5,9 @@ use std::convert::From;
use std::path::PathBuf;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana_ui::widget::Element;
use bitcoind::BitcoindSettingsState;
use wallet::WalletSettingsState;

View File

@ -3,17 +3,21 @@ use std::convert::From;
use std::path::PathBuf;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana::miniscript::bitcoin::{hashes::hex::ToHex, util::bip32::Fingerprint, Network};
use liana_ui::{
component::{form, modal},
widget::Element,
};
use crate::{
app::{
cache::Cache, error::Error, message::Message, settings, state::State, view, wallet::Wallet,
},
daemon::Daemon,
hw::{list_hardware_wallets, HardwareWallet, HardwareWalletConfig},
ui::component::{form, modal},
};
pub struct WalletSettingsState {

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana::{
descriptors::LianaDescInfo,
miniscript::bitcoin::{
@ -9,6 +9,11 @@ use liana::{
},
};
use liana_ui::{
component::{form, modal},
widget::Element,
};
use crate::{
app::{
cache::Cache,
@ -23,7 +28,6 @@ use crate::{
Daemon,
},
hw::{list_hardware_wallets, HardwareWallet},
ui::component::{form, modal},
};
trait Action {

View File

@ -2,9 +2,13 @@ pub mod detail;
mod step;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana::miniscript::bitcoin::{consensus, util::psbt::Psbt};
use liana_ui::{
component::{form, modal},
widget::Element,
};
use super::{redirect, State};
use crate::{
@ -13,7 +17,6 @@ use crate::{
model::{Coin, SpendTx},
Daemon,
},
ui::component::{form, modal},
};
pub struct SpendPanel {

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana::{
descriptors::MultipathDescriptor,
miniscript::bitcoin::{
@ -10,6 +10,8 @@ use liana::{
},
};
use liana_ui::{component::form, widget::Element};
use crate::{
app::{
cache::Cache, error::Error, message::Message, state::spend::detail, view, wallet::Wallet,
@ -18,7 +20,6 @@ use crate::{
model::{remaining_sequence, Coin, SpendTx},
Daemon,
},
ui::component::form,
};
/// See: https://github.com/wizardsardine/liana/blob/master/src/commands/mod.rs#L32

View File

@ -1,6 +1,11 @@
use iced::{
widget::{Button, Column, Container, Row},
Alignment, Element, Length,
use iced::{Alignment, Length};
use liana_ui::{
color,
component::{badge, separation, text::*},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
@ -9,12 +14,6 @@ use crate::{
view::{message::Message, util::*},
},
daemon::model::{remaining_sequence, Coin},
ui::{
color,
component::{badge, button, card, separation, text::*},
icon,
util::Collection,
},
};
pub fn coins_view<'a>(
@ -77,11 +76,11 @@ fn coin_list_view(
Some(Container::new(
Row::new()
.spacing(5)
.push(text(" 0").small().style(color::ALERT))
.push(text(" 0").small().style(color::legacy::ALERT))
.push(
icon::hourglass_done_icon()
.small()
.style(color::ALERT),
.style(color::legacy::ALERT),
)
.align_items(Alignment::Center),
))
@ -92,12 +91,12 @@ fn coin_list_view(
.push(
text(format!(" {}", seq))
.small()
.style(color::WARNING),
.style(color::legacy::WARNING),
)
.push(
icon::hourglass_icon()
.small()
.style(color::WARNING),
.style(color::legacy::WARNING),
)
.align_items(Alignment::Center),
))
@ -124,7 +123,7 @@ fn coin_list_view(
.align_items(Alignment::Center)
.spacing(20),
)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.padding(10)
.on_press(Message::Select(index)),
)
@ -144,7 +143,7 @@ fn coin_list_view(
text("The recovery path is available")
.bold()
.small()
.style(color::ALERT),
.style(color::legacy::ALERT),
))
} else {
Some(Container::new(
@ -169,7 +168,7 @@ fn coin_list_view(
.push(text(format!("{}", coin.outpoint)).small())
.push(Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(coin.outpoint.to_string()))
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
))
.spacing(5),
)
@ -204,5 +203,5 @@ fn coin_list_view(
None
}),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
}

View File

@ -1,18 +1,15 @@
use chrono::NaiveDateTime;
use iced::{
alignment,
widget::{Button, Column, Container, Row},
Alignment, Element, Length,
};
use iced::{alignment, Alignment, Length};
use crate::ui::{
color,
component::{badge, button::Style, card, text::*},
icon,
util::Collection,
};
use liana::miniscript::bitcoin;
use liana_ui::{
color,
component::{badge, card, text::*},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::{
@ -38,7 +35,11 @@ pub fn home_view<'a>(
Row::new()
.spacing(15)
.align_items(Alignment::Center)
.push(icon::hourglass_icon().size(30).style(color::WARNING))
.push(
icon::hourglass_icon()
.size(30)
.style(color::legacy::WARNING),
)
.push(
Row::new()
.spacing(5)
@ -56,7 +57,7 @@ pub fn home_view<'a>(
Row::new()
.spacing(15)
.align_items(Alignment::Center)
.push(icon::hourglass_done_icon().style(color::ALERT))
.push(icon::hourglass_done_icon().style(color::legacy::ALERT))
.push(
Row::new()
.spacing(5)
@ -97,11 +98,11 @@ pub fn home_view<'a>(
)
.width(Length::Fill)
.padding(15)
.style(Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.on_press(Message::Next),
)
.width(Length::Fill)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
)
} else {
None
@ -154,9 +155,9 @@ fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Element<'a, Mess
)
.padding(10)
.on_press(Message::Select(i))
.style(Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}
@ -203,7 +204,7 @@ pub fn event_view<'a>(cache: &Cache, event: &'a HistoryTransaction) -> Element<'
.push(
Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(event.tx.txid().to_string()))
.style(Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
)
.width(Length::Shrink),
),

View File

@ -1,21 +1,14 @@
use iced::{
widget::{tooltip, Button, Column, Container, Row},
Alignment, Element, Length,
use iced::{widget::tooltip, Alignment, Length};
use liana_ui::{
color,
component::text::{text, Text},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::view::message::*,
hw::HardwareWallet,
ui::{
color,
component::{
button, card,
text::{text, Text},
},
icon,
util::Collection,
},
};
use crate::{app::view::message::*, hw::HardwareWallet};
pub fn hw_list_view<'a>(
i: usize,
@ -57,7 +50,7 @@ pub fn hw_list_view<'a>(
message,
tooltip::Position::Bottom,
)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
),
})
.spacing(5)
@ -76,20 +69,20 @@ pub fn hw_list_view<'a>(
Row::new()
.align_items(Alignment::Center)
.spacing(5)
.push(icon::circle_check_icon().style(color::SUCCESS))
.push(text(v).style(color::SUCCESS))
.push(icon::circle_check_icon().style(color::legacy::SUCCESS))
.push(text(v).style(color::legacy::SUCCESS))
}))
.align_items(Alignment::Center)
.width(Length::Fill),
)
.padding(10)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.width(Length::Fill);
if !processing && hw.is_supported() {
bttn = bttn.on_press(Message::SelectHardwareWallet(i));
}
Container::new(bttn)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}

View File

@ -13,20 +13,19 @@ pub mod spend;
pub use message::*;
use warning::warn;
use iced::{
widget::{self, scrollable, Button, Column, Container, Row},
Element, Length,
};
use iced::{widget::scrollable, Length};
use crate::ui::{
component::{badge, button, container, separation, text::*},
use liana_ui::{
component::{button, separation, text::*},
icon::{coin_icon, cross_icon, home_icon, receive_icon, send_icon, settings_icon},
theme,
util::Collection,
widget::*,
};
use crate::app::{cache::Cache, error::Error, menu::Menu};
pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Message> {
pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
let home_button = if *menu == Menu::Home {
button::primary(Some(home_icon()), "Home")
.on_press(Message::Reload)
@ -63,7 +62,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.small()
.bold(),
)
.style(badge::PillStyle::InversePrimary),
.style(theme::Container::Pill(theme::Pill::InversePrimary)),
)
.spacing(10)
.width(iced::Length::Fill)
@ -73,7 +72,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Primary.into())
.style(theme::Button::Primary)
.on_press(Message::Reload)
.width(iced::Length::Units(200))
} else {
@ -102,7 +101,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.small()
.bold(),
)
.style(badge::PillStyle::Primary),
.style(theme::Pill::Primary),
)
.spacing(10)
.width(iced::Length::Fill)
@ -112,7 +111,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Menu(Menu::Coins))
.width(iced::Length::Units(200))
};
@ -138,7 +137,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.small()
.bold(),
)
.style(badge::PillStyle::InversePrimary),
.style(theme::Pill::InversePrimary),
)
})
.spacing(10)
@ -149,7 +148,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Primary.into())
.style(theme::Button::Primary)
.on_press(Message::Reload)
.width(iced::Length::Units(200))
} else {
@ -173,7 +172,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.small()
.bold(),
)
.style(badge::PillStyle::Primary),
.style(theme::Pill::Primary),
)
})
.spacing(10)
@ -184,7 +183,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.padding(5)
.center_x(),
)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Menu(Menu::Spend))
.width(iced::Length::Units(200))
};
@ -234,14 +233,14 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa
.push_maybe(cache.rescan_progress.map(|p| {
Container::new(text(format!(" Rescan...{:.2}% ", p * 100.0)))
.padding(5)
.style(badge::PillStyle::Simple)
.style(theme::Pill::Simple)
}))
.push(settings_button),
)
.height(Length::Shrink),
),
)
.style(container::Style::Sidebar)
.style(theme::Container::Foreground)
}
pub fn dashboard<'a, T: Into<Element<'a, Message>>>(
@ -268,9 +267,9 @@ pub fn dashboard<'a, T: Into<Element<'a, Message>>>(
.into()
}
fn main_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<'a, T> {
fn main_section<'a, T: 'a>(menu: Container<'a, T>) -> Container<'a, T> {
Container::new(menu.max_width(1500))
.style(container::Style::Background)
.style(theme::Container::Background)
.center_x()
.width(Length::Fill)
.height(Length::Fill)
@ -300,7 +299,7 @@ pub fn modal<'a, T: Into<Element<'a, Message>>, F: Into<Element<'a, Message>>>(
.push(button::primary(Some(cross_icon()), "Close").on_press(Message::Close)),
)
.padding(10)
.style(container::Style::Background),
.style(theme::Container::Background),
)
.push(modal_section(Container::new(scrollable(content))))
.push_maybe(fixed_footer)
@ -309,9 +308,9 @@ pub fn modal<'a, T: Into<Element<'a, Message>>, F: Into<Element<'a, Message>>>(
.into()
}
fn modal_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<'a, T> {
fn modal_section<'a, T: 'a>(menu: Container<'a, T>) -> Container<'a, T> {
Container::new(menu.max_width(1500))
.style(container::Style::Background)
.style(theme::Container::Background)
.center_x()
.width(Length::Fill)
.height(Length::Fill)

View File

@ -1,16 +1,14 @@
use iced::{
widget::{
qr_code::{self, QRCode},
Button, Column, Row,
},
Alignment, Element,
widget::qr_code::{self, QRCode},
Alignment,
};
use liana::miniscript::bitcoin;
use crate::ui::{
component::{button, card, text::*},
icon,
use liana_ui::{
component::{card, text::*},
icon, theme,
widget::*,
};
use super::message::Message;
@ -25,7 +23,7 @@ pub fn receive<'a>(address: &'a bitcoin::Address, qr: &'a qr_code::State) -> Ele
.push(
Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(address.to_string()))
.style(button::Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
)
.align_items(Alignment::Center),
)

View File

@ -1,19 +1,16 @@
use iced::{
widget::{Column, Container, Row, Space},
Alignment, Element, Length,
};
use iced::{widget::Space, Alignment, Length};
use liana::miniscript::bitcoin::Amount;
use crate::{
app::view::message::{CreateSpendMessage, Message},
ui::{
component::{button, form, text::*},
icon,
util::Collection,
},
use liana_ui::{
component::{button, form, text::*},
icon,
util::Collection,
widget::*,
};
use crate::app::view::message::{CreateSpendMessage, Message};
#[allow(clippy::too_many_arguments)]
pub fn recovery<'a>(
locked_coins: &(usize, Amount),

View File

@ -1,16 +1,20 @@
use std::collections::HashSet;
use std::str::FromStr;
use iced::{
alignment,
widget::{self, Button, Column, Container, ProgressBar, Row, Space},
Alignment, Element, Length,
};
use iced::{alignment, widget::Space, Alignment, Length};
use liana::miniscript::bitcoin::{util::bip32::Fingerprint, Network};
use super::{dashboard, message::*};
use liana_ui::{
color,
component::{badge, button, card, form, separation, text::*, tooltip::tooltip},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::{
cache::Cache,
@ -19,12 +23,6 @@ use crate::{
view::{hw, warning::warn},
},
hw::HardwareWallet,
ui::{
color,
component::{badge, button, card, form, separation, text::*, tooltip::tooltip},
icon,
util::Collection,
},
};
pub fn list(cache: &Cache) -> Element<Message> {
@ -37,7 +35,7 @@ pub fn list(cache: &Cache) -> Element<Message> {
.width(Length::Fill)
.push(
Button::new(text("Settings").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Menu(Menu::Settings)))
.push(
Container::new(
@ -51,11 +49,11 @@ pub fn list(cache: &Cache) -> Element<Message> {
.width(Length::Fill),
)
.width(Length::Fill)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::Settings(SettingsMessage::EditBitcoindSettings))
)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
)
.push(
Container::new(
@ -69,11 +67,11 @@ pub fn list(cache: &Cache) -> Element<Message> {
.width(Length::Fill),
)
.width(Length::Fill)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::Settings(SettingsMessage::EditWalletSettings))
)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
)
.push(
Container::new(
@ -88,11 +86,11 @@ pub fn list(cache: &Cache) -> Element<Message> {
.width(Length::Fill),
)
.width(Length::Fill)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::Menu(Menu::Recovery))
)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
)
.push(
Container::new(
@ -106,11 +104,11 @@ pub fn list(cache: &Cache) -> Element<Message> {
.width(Length::Fill),
)
.width(Length::Fill)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::Settings(SettingsMessage::AboutSection))
)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
)
)
}
@ -131,17 +129,17 @@ pub fn bitcoind_settings<'a>(
.align_items(Alignment::Center)
.push(
Button::new(text("Settings").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Menu(Menu::Settings)),
)
.push(icon::chevron_right().size(30))
.push(
Button::new(text("Bitcoin Core").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Settings(SettingsMessage::EditBitcoindSettings)),
),
)
.push(widget::Column::with_children(settings).spacing(20)),
.push(Column::with_children(settings).spacing(20)),
)
}
@ -162,13 +160,13 @@ pub fn about_section<'a>(
.align_items(Alignment::Center)
.push(
Button::new(text("Settings").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Menu(Menu::Settings)),
)
.push(icon::chevron_right().size(30))
.push(
Button::new(text("About").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Settings(SettingsMessage::AboutSection)),
),
)
@ -374,12 +372,11 @@ pub fn bitcoind<'a>(
.width(Length::Fill),
)
.push(if can_edit {
widget::Button::new(icon::pencil_icon())
.style(button::Style::TransparentBorder.into())
Button::new(icon::pencil_icon())
.style(theme::Button::TransparentBorder)
.on_press(SettingsEditMessage::Select)
} else {
widget::Button::new(icon::pencil_icon())
.style(button::Style::TransparentBorder.into())
Button::new(icon::pencil_icon()).style(theme::Button::TransparentBorder)
})
.align_items(Alignment::Center),
)
@ -391,20 +388,20 @@ pub fn bitcoind<'a>(
.into()
}
pub fn is_running_label<'a, T: 'a>(is_running: Option<bool>) -> widget::Container<'a, T> {
pub fn is_running_label<'a, T: 'a>(is_running: Option<bool>) -> Container<'a, T> {
if let Some(running) = is_running {
if running {
Container::new(
Row::new()
.push(icon::dot_icon().size(5).style(color::SUCCESS))
.push(text("Running").small().style(color::SUCCESS))
.push(icon::dot_icon().size(5).style(color::legacy::SUCCESS))
.push(text("Running").small().style(color::legacy::SUCCESS))
.align_items(Alignment::Center),
)
} else {
Container::new(
Row::new()
.push(icon::dot_icon().size(5).style(color::ALERT))
.push(text("Not running").small().style(color::ALERT))
.push(icon::dot_icon().size(5).style(color::legacy::ALERT))
.push(text("Not running").small().style(color::legacy::ALERT))
.align_items(Alignment::Center),
)
}
@ -429,7 +426,7 @@ pub fn rescan<'a>(
.push(badge::Badge::new(icon::block_icon()))
.push(text("Rescan blockchain").bold().width(Length::Fill))
.push_maybe(if success {
Some(text("Rescan was successful").style(color::SUCCESS))
Some(text("Rescan was successful").style(color::legacy::SUCCESS))
} else {
None
})
@ -539,13 +536,13 @@ pub fn wallet_settings<'a>(
.align_items(Alignment::Center)
.push(
Button::new(text("Settings").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Menu(Menu::Settings)),
)
.push(icon::chevron_right().size(30))
.push(
Button::new(text("Wallet").size(30).bold())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(Message::Settings(SettingsMessage::AboutSection)),
),
)
@ -604,8 +601,10 @@ pub fn wallet_settings<'a>(
Some(
Row::new()
.align_items(Alignment::Center)
.push(icon::circle_check_icon().style(color::SUCCESS))
.push(text("Updated").style(color::SUCCESS)),
.push(
icon::circle_check_icon().style(color::legacy::SUCCESS),
)
.push(text("Updated").style(color::legacy::SUCCESS)),
)
} else {
None

View File

@ -1,8 +1,8 @@
use std::collections::HashMap;
use iced::{
widget::{scrollable, tooltip, Button, Column, Container, Row, Scrollable, Space},
Alignment, Element, Length,
widget::{scrollable, tooltip, Space},
Alignment, Length,
};
use liana::{
@ -13,6 +13,19 @@ use liana::{
},
};
use liana_ui::{
color,
component::{
badge, button, card,
collapse::Collapse,
form, separation,
text::{text, Text},
},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::{
error::Error,
@ -20,17 +33,6 @@ use crate::{
},
daemon::model::{Coin, SpendStatus, SpendTx},
hw::HardwareWallet,
ui::{
color,
component::{
badge, button, card,
collapse::Collapse,
container, form, separation,
text::{text, Text},
},
icon,
util::Collection,
},
};
pub fn spend_view<'a>(
@ -178,16 +180,16 @@ pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
}),
)
.padding(10)
.style(container::Style::Background),
.style(theme::Container::Background),
)
.push(
Container::new(Scrollable::new(
Container::new(scrollable(
Container::new(Container::new(content).max_width(800))
.width(Length::Fill)
.center_x(),
))
.height(Length::Fill)
.style(container::Style::Background),
.style(theme::Container::Background),
)
.width(Length::Fill)
.height(Length::Fill)
@ -200,7 +202,7 @@ fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
.align_items(Alignment::Center)
.push(
Row::new()
.push(badge::Badge::new(icon::send_icon()).style(badge::Style::Standard))
.push(badge::Badge::new(icon::send_icon()).style(theme::Badge::Standard))
.push(if tx.sigs.recovery_path().is_some() {
text("Recovery").bold()
} else {
@ -266,14 +268,14 @@ fn spend_overview_view<'a>(
.on_press(Message::Clipboard(
tx.psbt.unsigned_tx.txid().to_string(),
))
.style(button::Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
)
.align_items(Alignment::Center),
),
)
.push(signatures(tx, desc_info, key_aliases)),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}
@ -286,12 +288,12 @@ pub fn signatures<'a>(
.push(
if let Some(sigs) = tx.path_ready() {
Container::new(
Scrollable::new(
scrollable(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
.push(icon::circle_check_icon().style(color::SUCCESS))
.push(text("Ready").bold().style(color::SUCCESS))
.push(icon::circle_check_icon().style(color::legacy::SUCCESS))
.push(text("Ready").bold().style(color::legacy::SUCCESS))
.push(text(", signed by"))
.push(
sigs.signed_pubkeys
@ -302,16 +304,16 @@ pub fn signatures<'a>(
tooltip::Tooltip::new(
Container::new(text(alias))
.padding(3)
.style(badge::PillStyle::Simple),
.style(theme::Container::Pill(theme::Pill::Simple)),
value.0.to_string(),
tooltip::Position::Bottom,
)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
)
} else {
Container::new(text(value.0.to_string()))
.padding(3)
.style(badge::PillStyle::Simple)
.style(theme::Container::Pill(theme::Pill::Simple))
})
}),
)
@ -335,7 +337,7 @@ pub fn signatures<'a>(
)
.padding(15)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
},
move || {
Button::new(
@ -353,7 +355,7 @@ pub fn signatures<'a>(
)
.padding(15)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
},
move || {
Into::<Element<'a, Message>>::into(
@ -423,11 +425,11 @@ pub fn path_view<'a>(
sigs.threshold - sigs.sigs_count
};
keys.sort();
Scrollable::new(
scrollable(
Row::new()
.align_items(Alignment::Center)
.push(if sigs.sigs_count >= sigs.threshold {
icon::circle_check_icon().style(color::SUCCESS)
icon::circle_check_icon().style(color::legacy::SUCCESS)
} else {
icon::circle_cross_icon()
})
@ -452,16 +454,16 @@ pub fn path_view<'a>(
tooltip::Tooltip::new(
Container::new(text(alias))
.padding(3)
.style(badge::PillStyle::Simple),
.style(theme::Container::Pill(theme::Pill::Simple)),
value.0.to_string(),
tooltip::Position::Bottom,
)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
)
} else {
Container::new(text(value.0.to_string()))
.padding(3)
.style(badge::PillStyle::Simple)
.style(theme::Container::Pill(theme::Pill::Simple))
})
} else {
None
@ -482,16 +484,16 @@ pub fn path_view<'a>(
tooltip::Tooltip::new(
Container::new(text(alias))
.padding(3)
.style(badge::PillStyle::Simple),
.style(theme::Container::Pill(theme::Pill::Simple)),
value.0.to_string(),
tooltip::Position::Bottom,
)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
)
} else {
Container::new(text(value.0.to_string()))
.padding(3)
.style(badge::PillStyle::Simple)
.style(theme::Container::Pill(theme::Pill::Simple))
})
}),
),
@ -531,7 +533,7 @@ pub fn inputs_and_outputs_view<'a>(
)
.padding(15)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
},
move || {
Button::new(
@ -550,7 +552,7 @@ pub fn inputs_and_outputs_view<'a>(
)
.padding(15)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
},
move || {
coins
@ -575,7 +577,7 @@ pub fn inputs_and_outputs_view<'a>(
coin.outpoint.to_string(),
))
.style(
button::Style::TransparentBorder.into(),
theme::Button::TransparentBorder,
),
),
)
@ -585,7 +587,7 @@ pub fn inputs_and_outputs_view<'a>(
.into()
},
))
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
)
} else {
None
@ -609,7 +611,7 @@ pub fn inputs_and_outputs_view<'a>(
)
.padding(15)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
},
move || {
Button::new(
@ -628,7 +630,7 @@ pub fn inputs_and_outputs_view<'a>(
)
.padding(15)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
},
move || {
tx.output
@ -655,7 +657,7 @@ pub fn inputs_and_outputs_view<'a>(
addr.to_string(),
))
.style(
button::Style::TransparentBorder.into(),
theme::Button::TransparentBorder,
),
),
)
@ -669,7 +671,7 @@ pub fn inputs_and_outputs_view<'a>(
Some(
Container::new(text("Change"))
.padding(5)
.style(badge::PillStyle::Success),
.style(theme::Container::Pill(theme::Pill::Success)),
)
} else {
None
@ -684,7 +686,7 @@ pub fn inputs_and_outputs_view<'a>(
Some(
Container::new(text("Deposit"))
.padding(5)
.style(badge::PillStyle::Success),
.style(theme::Container::Pill(theme::Pill::Success)),
)
} else {
None
@ -698,7 +700,7 @@ pub fn inputs_and_outputs_view<'a>(
.into()
},
))
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
),
)
.into()
@ -768,9 +770,10 @@ pub fn sign_action<'a>(
.align_items(Alignment::Center)
.spacing(5)
.push(
icon::circle_check_icon().style(color::SUCCESS),
icon::circle_check_icon()
.style(color::legacy::SUCCESS),
)
.push(text("Signed").style(color::SUCCESS)),
.push(text("Signed").style(color::legacy::SUCCESS)),
)
} else {
None
@ -778,7 +781,7 @@ pub fn sign_action<'a>(
)
.on_press(Message::Spend(SpendTxMessage::SelectHotSigner))
.padding(10)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.width(Length::Fill)
}))
.width(Length::Fill),
@ -844,7 +847,7 @@ pub fn update_spend_success_view<'a>() -> Element<'a, Message> {
Column::new()
.push(
card::simple(Container::new(
text("Spend transaction is updated").style(color::SUCCESS),
text("Spend transaction is updated").style(color::legacy::SUCCESS),
))
.padding(50),
)

View File

@ -1,20 +1,19 @@
pub mod detail;
pub mod step;
use iced::{
widget::{Button, Column, Container, Row, Space},
Alignment, Element, Length,
use iced::{widget::Space, Alignment, Length};
use liana_ui::{
color,
component::{badge, button, card, form, text::*},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::{error::Error, menu::Menu, view::util::*},
daemon::model::{SpendStatus, SpendTx},
ui::{
color,
component::{badge, button, card, form, text::*},
icon,
util::Collection,
},
};
use super::{message::*, warning::warn};
@ -57,7 +56,7 @@ pub fn import_spend_success_view<'a>() -> Element<'a, Message> {
Column::new()
.push(
card::simple(Container::new(
text("PSBT is imported").style(color::SUCCESS),
text("PSBT is imported").style(color::legacy::SUCCESS),
))
.padding(50),
)
@ -133,7 +132,7 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
.push(
Container::new(text(" Recovery ").small())
.padding(3)
.style(badge::PillStyle::Simple),
.style(theme::Container::Pill(theme::Pill::Simple)),
)
} else {
let sigs = tx.sigs.primary_path();
@ -172,8 +171,8 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
)
.padding(10)
.on_press(Message::Select(i))
.style(button::Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}

View File

@ -1,10 +1,18 @@
use iced::{
widget::{self, Button, Column, Container, Row},
Alignment, Element, Length,
};
use iced::{Alignment, Length};
use liana::miniscript::bitcoin::Amount;
use liana_ui::{
color,
component::{
badge, button, form,
text::{text, Text},
},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::{
cache::Cache,
@ -12,15 +20,6 @@ use crate::{
view::{message::*, modal, util::amount},
},
daemon::model::{remaining_sequence, Coin},
ui::{
color,
component::{
badge, button, card, form,
text::{text, Text},
},
icon,
util::Collection,
},
};
pub fn choose_recipients_view<'a>(
@ -37,7 +36,7 @@ pub fn choose_recipients_view<'a>(
.push(text("Choose recipients").bold().size(50))
.push(
Column::new()
.push(widget::Column::with_children(recipients).spacing(10))
.push(Column::with_children(recipients).spacing(10))
.push(
button::transparent(Some(icon::plus_icon()), "Add recipient")
.on_press(Message::CreateSpend(CreateSpendMessage::AddRecipient)),
@ -64,7 +63,10 @@ pub fn choose_recipients_view<'a>(
.width(Length::Fill),
)
.push_maybe(if duplicate {
Some(text("Two recipient addresses are the same").style(color::WARNING))
Some(
text("Two recipient addresses are the same")
.style(color::legacy::WARNING),
)
} else {
None
})
@ -213,9 +215,11 @@ fn coin_list_view<'a>(
Some(Container::new(
Row::new()
.spacing(5)
.push(text(" 0").small().style(color::ALERT))
.push(text(" 0").small().style(color::legacy::ALERT))
.push(
icon::hourglass_done_icon().small().style(color::ALERT),
icon::hourglass_done_icon()
.small()
.style(color::legacy::ALERT),
)
.align_items(Alignment::Center),
))
@ -224,9 +228,15 @@ fn coin_list_view<'a>(
Row::new()
.spacing(5)
.push(
text(format!(" {}", seq)).small().style(color::WARNING),
text(format!(" {}", seq))
.small()
.style(color::legacy::WARNING),
)
.push(
icon::hourglass_icon()
.small()
.style(color::legacy::WARNING),
)
.push(icon::hourglass_icon().small().style(color::WARNING))
.align_items(Alignment::Center),
))
} else {
@ -254,8 +264,8 @@ fn coin_list_view<'a>(
)
.padding(10)
.on_press(Message::CreateSpend(CreateSpendMessage::SelectCoin(i)))
.style(button::Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}

View File

@ -1,7 +1,6 @@
use iced::{widget::Row, Element};
use liana::miniscript::bitcoin::Amount;
use crate::ui::{color, component::text::*, util::Collection};
use liana_ui::{color, component::text::*, util::Collection, widget::*};
pub fn amount<'a, T: 'a>(a: &Amount) -> impl Into<Element<'a, T>> {
amount_with_size(a, TEXT_REGULAR_SIZE)

View File

@ -1,14 +1,12 @@
use std::convert::From;
use iced::{
widget::{self, Column, Container},
Length,
};
use iced::Length;
use liana_ui::{component::notification, widget::*};
use crate::{
app::error::Error,
daemon::{client::error::RpcErrorCode, DaemonError},
ui::component::notification,
};
/// Simple warning message displayed to non technical user.
@ -48,7 +46,7 @@ impl std::fmt::Display for WarningMessage {
}
}
pub fn warn<'a, T: 'a + Clone>(error: Option<&Error>) -> widget::Container<'a, T> {
pub fn warn<'a, T: 'a + Clone>(error: Option<&Error>) -> Container<'a, T> {
if let Some(w) = error {
let message: WarningMessage = w.into();
notification::warning(message.to_string(), w.to_string()).width(Length::Fill)

View File

@ -4,8 +4,9 @@ mod prompt;
mod step;
mod view;
use iced::{clipboard, Command, Element, Subscription};
use iced::{clipboard, Command, Subscription};
use liana::miniscript::bitcoin;
use liana_ui::widget::Element;
use tracing::{error, info, warn};
use context::Context;

View File

@ -3,7 +3,7 @@ use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana::{
descriptors::{LianaDescKeys, MultipathDescriptor},
miniscript::{
@ -17,6 +17,11 @@ use liana::{
},
};
use liana_ui::{
component::{form, modal::Modal},
widget::Element,
};
use async_hwi::DeviceKind;
use crate::{
@ -28,7 +33,6 @@ use crate::{
view, Error,
},
signer::Signer,
ui::component::{form, modal::Modal},
};
pub trait DescriptorKeyModal {

View File

@ -1,9 +1,11 @@
use std::collections::HashSet;
use std::sync::Arc;
use iced::{Command, Element};
use iced::Command;
use liana::{bip39, signer::HotSigner};
use liana_ui::widget::Element;
use crate::{
installer::{context::Context, message::Message, step::Step, view},
signer::Signer,

View File

@ -10,10 +10,10 @@ pub use mnemonic::{BackupMnemonic, RecoverMnemonic};
use std::path::PathBuf;
use std::str::FromStr;
use iced::{Command, Element};
use iced::Command;
use liana::{config::BitcoindConfig, miniscript::bitcoin};
use crate::ui::component::form;
use liana_ui::{component::form, widget::*};
use crate::installer::{
context::Context,

View File

@ -1,12 +1,22 @@
use iced::widget::{
scrollable::Properties, Button, Checkbox, Column, Container, PickList, Row, Scrollable, Space,
TextInput,
checkbox, container, pick_list, scrollable, scrollable::Properties, Space, TextInput,
};
use iced::{alignment, Alignment, Element, Length};
use iced::{alignment, Alignment, Length};
use std::collections::HashSet;
use liana::miniscript::bitcoin;
use liana_ui::{
color,
component::{
button, card, collapse, form, separation,
text::{text, Text},
tooltip,
},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
hw::HardwareWallet,
@ -15,16 +25,6 @@ use crate::{
message::{self, Message},
prompt, Error,
},
ui::{
color,
component::{
button, card, collapse, container, form, separation,
text::{text, Text},
tooltip,
},
icon,
util::Collection,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -92,7 +92,7 @@ pub fn welcome<'a>() -> Element<'a, Message> {
)
.padding(20),
)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::CreateWallet),
)
.push(
@ -106,7 +106,7 @@ pub fn welcome<'a>() -> Element<'a, Message> {
)
.padding(20),
)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::ParticipateWallet),
)
.push(
@ -120,7 +120,7 @@ pub fn welcome<'a>() -> Element<'a, Message> {
)
.padding(20),
)
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::ImportWallet),
),
)
@ -153,10 +153,11 @@ pub fn define_descriptor<'a>(
.spacing(10)
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
PickList::new(&NETWORKS[..], Some(Network::from(network)), |net| {
.push(container(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(theme::PickList::Simple)
.padding(10),
))
.push_maybe(if network_valid {
@ -195,7 +196,7 @@ pub fn define_descriptor<'a>(
None
})
.push(
Scrollable::new(
scrollable(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
@ -210,7 +211,7 @@ pub fn define_descriptor<'a>(
)
.width(Length::Units(200))
.height(Length::Units(200))
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.on_press(
Message::DefineDescriptor(
message::DefineDescriptor::AddKey(false),
@ -252,7 +253,7 @@ pub fn define_descriptor<'a>(
None
})
.push(
Scrollable::new(
scrollable(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
@ -267,7 +268,7 @@ pub fn define_descriptor<'a>(
)
.width(Length::Units(200))
.height(Length::Units(200))
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.on_press(
Message::DefineDescriptor(
message::DefineDescriptor::AddKey(true),
@ -359,7 +360,7 @@ pub fn import_descriptor<'a>(
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
PickList::new(&NETWORKS[..], Some(Network::from(network)), |net| {
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.padding(10),
@ -430,7 +431,7 @@ pub fn signer_xpubs(xpubs: &Vec<String>) -> Element<Message> {
)
.on_press(Message::UseHotSigner)
.padding(10)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.width(Length::Fill),
)
.push_maybe(if xpubs.is_empty() {
@ -448,7 +449,7 @@ pub fn signer_xpubs(xpubs: &Vec<String>) -> Element<Message> {
.align_items(Alignment::Center)
.push(
Container::new(
Scrollable::new(Container::new(text(xpub).small()).padding(10))
scrollable(Container::new(text(xpub).small()).padding(10))
.horizontal_scroll(
Properties::new().width(2).scroller_width(2),
),
@ -478,7 +479,7 @@ pub fn signer_xpubs(xpubs: &Vec<String>) -> Element<Message> {
None
}),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}
@ -523,7 +524,7 @@ pub fn hardware_wallet_xpubs<'a>(
message,
iced::widget::tooltip::Position::Bottom,
)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
),
})
.spacing(5)
@ -534,16 +535,16 @@ pub fn hardware_wallet_xpubs<'a>(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
.push(icon::warning_icon().style(color::ALERT))
.push(text("An error occured").style(color::ALERT)),
.push(icon::warning_icon().style(color::legacy::ALERT))
.push(text("An error occured").style(color::legacy::ALERT)),
e,
iced::widget::tooltip::Position::Bottom,
)
.style(card::ErrorCardStyle)
.style(theme::Container::Card(theme::Card::Error))
})),
)
.padding(10)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.width(Length::Fill);
if !processing && hw.is_supported() {
bttn = bttn.on_press(Message::Select(i));
@ -566,7 +567,7 @@ pub fn hardware_wallet_xpubs<'a>(
.align_items(Alignment::Center)
.push(
Container::new(
Scrollable::new(Container::new(text(xpub).small()).padding(10))
scrollable(Container::new(text(xpub).small()).padding(10))
.horizontal_scroll(
Properties::new().width(2).scroller_width(2),
),
@ -598,7 +599,7 @@ pub fn hardware_wallet_xpubs<'a>(
None
}),
)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}
@ -615,7 +616,7 @@ pub fn participate_xpub<'a>(
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
PickList::new(&NETWORKS[..], Some(Network::from(network)), |net| {
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.padding(10),
@ -658,7 +659,7 @@ pub fn participate_xpub<'a>(
.push(signer)
.width(Length::Fill),
)
.push(Checkbox::new(
.push(checkbox(
"I have shared my public keys",
shared,
Message::UserActionDone,
@ -744,7 +745,7 @@ pub fn register_descriptor<'a>(
)
.width(Length::Fill),
)
.push(Checkbox::new(
.push(checkbox(
"I have registered the descriptor on my device(s)",
done,
Message::UserActionDone,
@ -789,7 +790,7 @@ pub fn backup_descriptor<'a>(
.push(text("Learn more").small().bold())
.push(icon::collapse_icon()),
)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
},
|| {
Button::new(
@ -799,7 +800,7 @@ pub fn backup_descriptor<'a>(
.push(text("Learn more").small().bold())
.push(icon::collapsed_icon()),
)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
},
help_backup,
))
@ -818,7 +819,7 @@ pub fn backup_descriptor<'a>(
.spacing(10)
.max_width(1000),
))
.push(Checkbox::new(
.push(checkbox(
"I have backed up my descriptor",
done,
Message::UserActionDone,
@ -1051,7 +1052,7 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> {
.push(Space::with_width(Length::Fill))
.push(
Button::new(icon::cross_icon())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(message::DefineKey::Delete),
),
)
@ -1061,7 +1062,7 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> {
.spacing(15)
.align_items(Alignment::Center)
.push(
Scrollable::new(
scrollable(
icon::key_icon()
.style(color::DARK_GREY)
.size(50)
@ -1069,7 +1070,11 @@ pub fn undefined_descriptor_key<'a>() -> Element<'a, message::DefineKey> {
)
.horizontal_scroll(Properties::new().width(2).scroller_width(2)),
)
.push(icon::circle_check_icon().style(color::FOREGROUND).size(50)),
.push(
icon::circle_check_icon()
.style(color::legacy::FOREGROUND)
.size(50),
),
)
.height(Length::Fill)
.align_y(alignment::Vertical::Center),
@ -1100,7 +1105,7 @@ pub fn defined_descriptor_key(
.push(Space::with_width(Length::Fill))
.push(
Button::new(icon::cross_icon())
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.on_press(message::DefineKey::Delete),
),
)
@ -1114,13 +1119,13 @@ pub fn defined_descriptor_key(
.spacing(15)
.align_items(Alignment::Center)
.push(
Scrollable::new(text(name).bold()).horizontal_scroll(
scrollable(text(name).bold()).horizontal_scroll(
Properties::new().width(2).scroller_width(2),
),
)
.push(
icon::circle_check_icon()
.style(color::SUCCESS)
.style(color::legacy::SUCCESS)
.size(40)
.width(Length::Units(50)),
),
@ -1145,7 +1150,7 @@ pub fn defined_descriptor_key(
.push(
text("Key is for a different network")
.small()
.style(color::ALERT),
.style(color::legacy::ALERT),
)
.into()
} else if duplicate_key {
@ -1157,7 +1162,7 @@ pub fn defined_descriptor_key(
.height(Length::Units(200))
.width(Length::Units(200)),
)
.push(text("Duplicate key").small().style(color::ALERT))
.push(text("Duplicate key").small().style(color::legacy::ALERT))
.into()
} else if duplicate_name {
Column::new()
@ -1168,7 +1173,7 @@ pub fn defined_descriptor_key(
.height(Length::Units(200))
.width(Length::Units(200)),
)
.push(text("Duplicate name").small().style(color::ALERT))
.push(text("Duplicate name").small().style(color::legacy::ALERT))
.into()
} else {
card::simple(col)
@ -1243,7 +1248,7 @@ pub fn edit_key_modal<'a>(
.width(Length::Fill),
)
.push_maybe(if chosen_signer {
Some(icon::circle_check_icon().style(color::SUCCESS))
Some(icon::circle_check_icon().style(color::legacy::SUCCESS))
} else {
None
})
@ -1251,7 +1256,7 @@ pub fn edit_key_modal<'a>(
)
.width(Length::Fill)
.on_press(Message::UseHotSigner)
.style(button::Style::Border.into()),
.style(theme::Button::Secondary),
)
.width(Length::Fill),
)
@ -1382,7 +1387,7 @@ fn hw_list_view(
message,
iced::widget::tooltip::Position::Bottom,
)
.style(card::SimpleCardStyle),
.style(theme::Container::Card(theme::Card::Simple)),
),
})
.spacing(5)
@ -1398,7 +1403,7 @@ fn hw_list_view(
None
})
.push_maybe(if registered {
Some(Column::new().push(icon::circle_check_icon().style(color::SUCCESS)))
Some(Column::new().push(icon::circle_check_icon().style(color::legacy::SUCCESS)))
} else {
None
})
@ -1406,14 +1411,14 @@ fn hw_list_view(
.width(Length::Fill),
)
.padding(10)
.style(button::Style::TransparentBorder.into())
.style(theme::Button::TransparentBorder)
.width(Length::Fill);
if !processing && hw.is_supported() {
bttn = bttn.on_press(Message::Select(i));
}
Container::new(bttn)
.width(Length::Fill)
.style(card::SimpleCardStyle)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}
@ -1443,7 +1448,7 @@ pub fn backup_mnemonic<'a>(
)
}),
)
.push(Checkbox::new(
.push(checkbox(
"I have backed up my mnemonic",
done,
Message::UserActionDone,
@ -1485,7 +1490,7 @@ pub fn recover_mnemonic<'a>(
suggestions.iter().fold(Row::new().spacing(5), |row, sugg| {
row.push(
Button::new(text(sugg))
.style(button::Style::Border.into())
.style(theme::Button::Secondary)
.on_press(Message::MnemonicWord(
current,
sugg.to_string(),
@ -1516,7 +1521,10 @@ pub fn recover_mnemonic<'a>(
.width(Length::Units(100)),
)
.push_maybe(if *valid {
Some(icon::circle_check_icon().style(color::SUCCESS))
Some(
icon::circle_check_icon()
.style(color::legacy::SUCCESS),
)
} else {
None
}),
@ -1524,7 +1532,9 @@ pub fn recover_mnemonic<'a>(
},
))
.push(Space::with_height(Length::Units(50)))
.push_maybe(error.map(|e| card::invalid(text(e).style(color::ALERT)))),
.push_maybe(
error.map(|e| card::invalid(text(e).style(color::legacy::ALERT))),
),
)
} else {
None
@ -1572,7 +1582,7 @@ fn layout<'a>(
progress: (usize, usize),
content: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
Container::new(Scrollable::new(
Container::new(scrollable(
Column::new()
.push(
Container::new(button::transparent(None, "< Previous").on_press(Message::Previous))
@ -1588,19 +1598,15 @@ fn layout<'a>(
.center_x()
.height(Length::Fill)
.width(Length::Fill)
.style(container::Style::Background)
.style(theme::Container::Background)
.into()
}
mod threshsold_input {
use crate::ui::{
component::{button, text::*},
icon,
};
use iced::alignment::{self, Alignment};
use iced::widget::{Button, Column, Container};
use iced::{Element, Length};
use iced::Length;
use iced_lazy::{self, Component};
use liana_ui::{component::text::*, icon, theme, widget::*};
pub struct ThresholdInput<Message> {
value: usize,
@ -1636,7 +1642,7 @@ mod threshsold_input {
}
}
impl<Message> Component<Message, iced::Renderer> for ThresholdInput<Message> {
impl<Message> Component<Message, iced::Renderer<theme::Theme>> for ThresholdInput<Message> {
type State = ();
type Event = Event;
@ -1662,7 +1668,7 @@ mod threshsold_input {
fn view(&self, _state: &Self::State) -> Element<Self::Event> {
let button = |label, on_press| {
Button::new(label)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
.width(Length::Units(50))
.on_press(on_press)
};

View File

@ -1,21 +1,17 @@
use std::path::PathBuf;
use iced::{
widget::{Button, Column, Container, Row},
Alignment, Command, Element, Length, Subscription,
};
use iced::{Alignment, Command, Length, Subscription};
use liana::{config::ConfigError, miniscript::bitcoin::Network};
use crate::{
app,
ui::{
component::{badge, button, card, text::*},
icon,
util::*,
},
use liana_ui::{
component::{badge, card, text::*},
icon, theme,
util::*,
widget::*,
};
use crate::app;
pub struct Launcher {
choices: Vec<Network>,
datadir_path: PathBuf,
@ -102,9 +98,9 @@ impl Launcher {
badge::Badge::new(icon::bitcoin_icon()).style(
match choice {
Network::Bitcoin => {
badge::Style::Bitcoin
theme::Badge::Bitcoin
}
_ => badge::Style::Standard,
_ => theme::Badge::Standard,
},
),
)
@ -118,7 +114,7 @@ impl Launcher {
.on_press(ViewMessage::Check(*choice))
.padding(10)
.width(Length::Fill)
.style(button::Style::Border.into()),
.style(theme::Button::Secondary),
)
},
)
@ -133,7 +129,7 @@ impl Launcher {
.on_press(ViewMessage::StartInstall)
.padding(10)
.width(Length::Fill)
.style(button::Style::TransparentBorder.into()),
.style(theme::Button::TransparentBorder),
),
)
.max_width(500)

View File

@ -6,7 +6,6 @@ pub mod launcher;
pub mod loader;
pub mod logger;
pub mod signer;
pub mod ui;
pub mod utils;
use liana::Version;

View File

@ -3,10 +3,6 @@ use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use iced::{
widget::{Column, Container, ProgressBar, Row},
Element,
};
use iced::{Alignment, Command, Length, Subscription};
use tracing::{debug, info};
@ -15,6 +11,12 @@ use liana::{
miniscript::bitcoin,
StartupError,
};
use liana_ui::{
component::{button, notification, text::*},
icon,
util::Collection,
widget::*,
};
use crate::{
app::{
@ -23,11 +25,6 @@ use crate::{
wallet::{Wallet, WalletError},
},
daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError},
ui::{
component::{button, notification, text::*},
icon,
util::Collection,
},
};
type Lianad = client::Lianad<client::jsonrpc::JsonRPCClient>;

View File

@ -2,13 +2,14 @@
use std::{error::Error, io::Write, path::PathBuf, str::FromStr};
use iced::{executor, Application, Command, Element, Settings, Subscription};
use iced::{executor, Application, Command, Settings, Subscription};
use tracing::{error, info};
use tracing_subscriber::filter::LevelFilter;
extern crate serde;
extern crate serde_json;
use liana::{config::Config as DaemonConfig, miniscript::bitcoin};
use liana_ui::{theme, widget::Element};
use liana_gui::{
app::{
@ -87,7 +88,7 @@ impl Application for GUI {
type Executor = executor::Default;
type Message = Message;
type Flags = Config;
type Theme = iced::Theme;
type Theme = theme::Theme;
fn title(&self) -> String {
match self.state {

View File

@ -1,81 +0,0 @@
use iced::Color;
pub const BACKGROUND: Color = Color::from_rgb(
0xF6 as f32 / 255.0,
0xF7 as f32 / 255.0,
0xF8 as f32 / 255.0,
);
pub const BORDER_GREY: Color = Color::from_rgb(
0xd0 as f32 / 255.0,
0xd7 as f32 / 255.0,
0xde as f32 / 255.0,
);
pub const FOREGROUND: Color = Color::WHITE;
pub const PRIMARY: Color = Color::BLACK;
pub const SECONDARY: Color = DARK_GREY;
pub const SUCCESS: Color = Color::from_rgb(
0x29 as f32 / 255.0,
0xBC as f32 / 255.0,
0x97 as f32 / 255.0,
);
#[allow(dead_code)]
pub const SUCCESS_LIGHT: Color = Color::from_rgba(
0x29 as f32 / 255.0,
0xBC as f32 / 255.0,
0x97 as f32 / 255.0,
0.5f32,
);
pub const ALERT: Color = Color::from_rgb(
0xF0 as f32 / 255.0,
0x43 as f32 / 255.0,
0x59 as f32 / 255.0,
);
pub const ALERT_LIGHT: Color = Color::from_rgba(
0xF0 as f32 / 255.0,
0x43 as f32 / 255.0,
0x59 as f32 / 255.0,
0.5f32,
);
pub const WARNING: Color =
Color::from_rgb(0xFF as f32 / 255.0, 0xa7 as f32 / 255.0, 0x0 as f32 / 255.0);
pub const WARNING_LIGHT: Color = Color::from_rgba(
0xFF as f32 / 255.0,
0xa7 as f32 / 255.0,
0x0 as f32 / 255.0,
0.5f32,
);
pub const CANCEL: Color = Color::from_rgb(
0x34 as f32 / 255.0,
0x37 as f32 / 255.0,
0x3D as f32 / 255.0,
);
pub const INFO: Color = Color::from_rgb(
0x2A as f32 / 255.0,
0x98 as f32 / 255.0,
0xBD as f32 / 255.0,
);
pub const INFO_LIGHT: Color = Color::from_rgba(
0x2A as f32 / 255.0,
0x98 as f32 / 255.0,
0xBD as f32 / 255.0,
0.5f32,
);
pub const DARK_GREY: Color = Color::from_rgb(
0x8c as f32 / 255.0,
0x97 as f32 / 255.0,
0xa6 as f32 / 255.0,
);

View File

@ -1,209 +0,0 @@
use iced::{
widget::{self, tooltip, Container},
Element, Length,
};
use crate::ui::{
color,
component::{card, text::*},
icon,
};
pub enum Style {
Standard,
Success,
Warning,
Bitcoin,
}
impl widget::container::StyleSheet for Style {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
match self {
Self::Standard => widget::container::Appearance {
border_radius: 40.0,
background: color::BACKGROUND.into(),
..widget::container::Appearance::default()
},
Self::Success => widget::container::Appearance {
border_radius: 40.0,
background: color::SUCCESS_LIGHT.into(),
text_color: color::SUCCESS.into(),
..widget::container::Appearance::default()
},
Self::Warning => widget::container::Appearance {
border_radius: 40.0,
background: color::WARNING_LIGHT.into(),
text_color: color::WARNING.into(),
..widget::container::Appearance::default()
},
Self::Bitcoin => widget::container::Appearance {
border_radius: 40.0,
background: color::WARNING.into(),
text_color: iced::Color::WHITE.into(),
..widget::container::Appearance::default()
},
}
}
}
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 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,
}
}
pub fn style(self, style: Style) -> Self {
Self {
icon: self.icon,
style,
}
}
}
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)
.center_x()
.center_y()
.into()
}
}
pub fn receive<T>() -> widget::container::Container<'static, T> {
Container::new(icon::receive_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(Style::Standard)
.center_x()
.center_y()
}
pub fn spend<T>() -> widget::container::Container<'static, T> {
Container::new(icon::send_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(Style::Standard)
.center_x()
.center_y()
}
pub fn coin<T>() -> widget::container::Container<'static, T> {
Container::new(icon::coin_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(Style::Standard)
.center_x()
.center_y()
}
pub enum PillStyle {
InversePrimary,
Primary,
Success,
Simple,
}
impl widget::container::StyleSheet for PillStyle {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
match self {
Self::Primary => widget::container::Appearance {
background: color::PRIMARY.into(),
border_radius: 10.0,
text_color: iced::Color::WHITE.into(),
..widget::container::Appearance::default()
},
Self::InversePrimary => widget::container::Appearance {
background: color::FOREGROUND.into(),
border_radius: 10.0,
text_color: color::PRIMARY.into(),
..widget::container::Appearance::default()
},
Self::Success => widget::container::Appearance {
background: color::SUCCESS.into(),
border_radius: 10.0,
text_color: iced::Color::WHITE.into(),
..widget::container::Appearance::default()
},
Self::Simple => widget::container::Appearance {
background: color::BACKGROUND.into(),
border_radius: 10.0,
text_color: iced::Color::BLACK.into(),
..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())
}
}
pub fn unconfirmed<'a, T: 'a>() -> widget::container::Container<'a, T> {
Container::new(
tooltip::Tooltip::new(
Container::new(text(" Unconfirmed ").small())
.padding(3)
.style(PillStyle::Simple),
"Do not treat this as a payment until it is confirmed",
tooltip::Position::Top,
)
.style(card::SimpleCardStyle),
)
}
pub fn deprecated<'a, T: 'a>() -> widget::container::Container<'a, T> {
Container::new(
tooltip::Tooltip::new(
Container::new(text(" Deprecated ").small())
.padding(3)
.style(PillStyle::Simple),
"This spend cannot be included anymore in the blockchain",
tooltip::Position::Top,
)
.style(card::SimpleCardStyle),
)
}
pub fn spent<'a, T: 'a>() -> widget::container::Container<'a, T> {
Container::new(
tooltip::Tooltip::new(
Container::new(text(" Spent ").small())
.padding(3)
.style(PillStyle::Simple),
"The spend transaction was included in the blockchain",
tooltip::Position::Top,
)
.style(card::SimpleCardStyle),
)
}

View File

@ -1,151 +0,0 @@
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<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<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<Text<'a>>, t: &'static str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Transparent.into())
}
pub fn border<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> button::Button<'a, T> {
button::Button::new(content(icon, t)).style(Style::Border.into())
}
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<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::new()
.push(i)
.push(text(t))
.spacing(10)
.width(iced::Length::Fill)
.align_items(Alignment::Center),
)
.width(iced::Length::Fill)
.center_x()
.padding(5),
}
}
#[derive(Debug, Clone, Copy)]
pub enum Style {
Primary,
Transparent,
TransparentBorder,
Border,
Destructive,
}
impl button::StyleSheet for Style {
type Style = iced::Theme;
fn active(&self, _style: &Self::Style) -> button::Appearance {
match self {
Style::Primary => button::Appearance {
shadow_offset: Vector::default(),
background: color::PRIMARY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: color::FOREGROUND,
},
Style::Destructive => button::Appearance {
shadow_offset: Vector::default(),
background: color::FOREGROUND.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: color::ALERT,
text_color: color::ALERT,
},
Style::Transparent | Style::TransparentBorder => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: Color::BLACK,
},
Style::Border => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.2,
border_color: color::BORDER_GREY,
text_color: Color::BLACK,
},
}
}
fn hovered(&self, _style: &Self::Style) -> button::Appearance {
match self {
Style::Primary => button::Appearance {
shadow_offset: Vector::default(),
background: color::PRIMARY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: color::FOREGROUND,
},
Style::Destructive => button::Appearance {
shadow_offset: Vector::default(),
background: color::FOREGROUND.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: color::ALERT,
text_color: color::ALERT,
},
Style::Transparent => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: Color::BLACK,
},
Style::TransparentBorder => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: Color::BLACK,
text_color: Color::BLACK,
},
Style::Border => button::Appearance {
shadow_offset: Vector::default(),
background: Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: Color::BLACK,
text_color: Color::BLACK,
},
}
}
}
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,149 +0,0 @@
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::new(content).padding(15).style(SimpleCardStyle)
}
pub struct SimpleCardStyle;
impl widget::container::StyleSheet for SimpleCardStyle {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
widget::container::Appearance {
border_radius: 10.0,
border_color: color::BORDER_GREY,
border_width: 1.0,
background: color::FOREGROUND.into(),
..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())
}
}
pub fn invalid<'a, T: 'a, C: Into<Element<'a, T>>>(content: C) -> widget::Container<'a, T> {
Container::new(content).padding(15).style(InvalidCardStyle)
}
pub struct InvalidCardStyle;
impl widget::container::StyleSheet for InvalidCardStyle {
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.0,
background: color::FOREGROUND.into(),
..widget::container::Appearance::default()
}
}
}
impl From<InvalidCardStyle> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: InvalidCardStyle) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<InvalidCardStyle> for iced::theme::Container {
fn from(i: InvalidCardStyle) -> 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: String) -> widget::Container<'a, T> {
Container::new(
Row::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.push(icon::warning_octagon_icon().style(color::WARNING))
.push(text(message).style(color::WARNING)),
)
.padding(15)
.style(WarningCardStyle)
}
pub struct WarningCardStyle;
impl widget::container::StyleSheet for WarningCardStyle {
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::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: &'static str, error: String) -> widget::Container<'a, T> {
Container::new(
Tooltip::new(
Row::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.push(icon::warning_icon().style(color::ALERT))
.push(text(message).style(color::ALERT)),
error,
widget::tooltip::Position::Bottom,
)
.style(ErrorCardStyle),
)
.padding(15)
.style(ErrorCardStyle)
}
pub struct ErrorCardStyle;
impl widget::container::StyleSheet for ErrorCardStyle {
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::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,85 +0,0 @@
use iced::{
widget::{Button, Column},
Element,
};
use iced_lazy::{self, Component};
use std::marker::PhantomData;
pub struct Collapse<'a, M, H, F, C> {
before: H,
after: F,
content: C,
phantom: PhantomData<&'a M>,
}
impl<'a, Message, T, H, F, C> Collapse<'a, Message, H, F, C>
where
Message: 'a,
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>> + 'a,
F: Fn() -> Button<'a, Event<T>> + 'a,
C: Fn() -> Element<'a, T> + 'a,
{
pub fn new(before: H, after: F, content: C) -> Self {
Collapse {
before,
after,
content,
phantom: PhantomData,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Event<T> {
Internal(T),
Collapse(bool),
}
impl<'a, Message, T, H, F, C> Component<Message, iced::Renderer> for Collapse<'a, Message, H, F, C>
where
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>>,
F: Fn() -> Button<'a, Event<T>>,
C: Fn() -> Element<'a, T>,
{
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> {
if *state {
Column::new()
.push((self.after)().on_press(Event::Collapse(false)))
.push((self.content)().map(Event::Internal))
.into()
} else {
Column::new()
.push((self.before)().on_press(Event::Collapse(true)))
.into()
}
}
}
impl<'a, Message, T, H: 'a, F: 'a, C: 'a> From<Collapse<'a, Message, H, F, C>>
for Element<'a, Message>
where
Message: 'a,
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>, iced::Renderer>,
F: Fn() -> Button<'a, Event<T>, iced::Renderer>,
C: Fn() -> Element<'a, T, iced::Renderer>,
{
fn from(c: Collapse<'a, Message, H, F, C>) -> Self {
iced_lazy::component(c)
}
}

View File

@ -1,37 +0,0 @@
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::BORDER_GREY,
..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,137 +0,0 @@
use iced::{
widget::{
text_input::{Appearance, StyleSheet, TextInput},
Column, Container,
},
Element, Length,
};
use crate::ui::{color, component::text::*, util::Collection};
#[derive(Debug, Clone)]
pub struct Value<T> {
pub value: T,
pub valid: bool,
}
impl std::default::Default for Value<String> {
fn default() -> Self {
Self {
value: "".to_string(),
valid: true,
}
}
}
pub struct Form<'a, Message> {
input: TextInput<'a, Message>,
warning: Option<&'a str>,
valid: bool,
}
impl<'a, Message: 'a> Form<'a, Message>
where
Message: Clone,
{
/// Creates a new [`Form`].
///
/// It expects:
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`Form`] changes
pub fn new<F>(placeholder: &str, value: &Value<String>, on_change: F) -> Self
where
F: 'static + Fn(String) -> Message,
{
Self {
input: TextInput::new(placeholder, &value.value, on_change),
warning: None,
valid: value.valid,
}
}
/// Sets the [`Form`] with a warning message
pub fn warning(mut self, warning: &'a str) -> Self {
self.warning = Some(warning);
self
}
/// Sets the padding of the [`Form`].
pub fn padding(mut self, units: u16) -> Self {
self.input = self.input.padding(units);
self
}
/// Sets the [`Form`] with a text size
pub fn size(mut self, size: u16) -> Self {
self.input = self.input.size(size);
self
}
}
impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
fn from(form: Form<'a, Message>) -> Element<'a, Message> {
Container::new(
Column::new()
.push(if !form.valid {
form.input.style(InvalidFormStyle)
} else {
form.input
})
.push_maybe(if !form.valid {
form.warning
.map(|message| text(message).style(color::ALERT).small())
} else {
None
})
.width(Length::Fill)
.spacing(5),
)
.width(Length::Fill)
.into()
}
}
struct InvalidFormStyle;
impl StyleSheet for InvalidFormStyle {
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,
border_color: color::ALERT,
}
}
fn focused(&self, style: &Self::Style) -> Appearance {
Appearance {
border_color: color::ALERT,
..self.active(style)
}
}
fn placeholder_color(&self, _style: &Self::Style) -> iced::Color {
iced::Color::from_rgb(0.7, 0.7, 0.7)
}
fn value_color(&self, _style: &Self::Style) -> iced::Color {
iced::Color::from_rgb(0.3, 0.3, 0.3)
}
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,46 +0,0 @@
pub mod badge;
pub mod button;
pub mod card;
pub mod collapse;
pub mod container;
pub mod form;
pub mod modal;
pub mod notification;
pub mod text;
pub mod tooltip;
pub use tooltip::tooltip;
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(Text::new(" ")))
.style(SepStyle)
.height(Length::Units(1))
}
pub struct SepStyle;
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::BORDER_GREY.into(),
..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,279 +0,0 @@
/// modal widget from https://github.com/iced-rs/iced/blob/master/examples/modal/
use iced_native::alignment::Alignment;
use iced_native::widget::{self, Tree};
use iced_native::{
event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length,
Point, Rectangle, Shell, Size, Widget,
};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
Self {
base: base.into(),
modal: modal.into(),
on_blur: None,
}
}
/// Sets the message that will be produces when the background
/// of the [`Modal`] is pressed
pub fn on_blur(self, on_blur: Option<Message>) -> Self {
Self { on_blur, ..self }
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.base
.as_widget()
.operate(&mut state.children[0], layout, renderer, operation);
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event {
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
return event::Status::Captured;
}
}
}
self.content.as_widget_mut().on_event(
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor_position,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
renderer,
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
Element::new(modal)
}
}

View File

@ -1,78 +0,0 @@
use crate::ui::{
color,
component::{button, collapse, text::*},
icon,
};
use iced::{
widget::{container, Button, Container, Row},
Alignment, Element, Length,
};
pub fn warning<'a, T: 'a + Clone>(message: String, error: String) -> Container<'a, T> {
let message_clone = message.clone();
Container::new(Container::new(collapse::Collapse::new(
move || {
Button::new(
Row::new()
.push(
Container::new(text(message_clone.to_string()).small().bold())
.width(Length::Fill),
)
.push(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Learn more").small().bold())
.push(icon::collapse_icon()),
),
)
.style(button::Style::Transparent.into())
},
move || {
Button::new(
Row::new()
.push(
Container::new(text(message.to_owned()).small().bold()).width(Length::Fill),
)
.push(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Learn more").small().bold())
.push(icon::collapsed_icon()),
),
)
.style(button::Style::Transparent.into())
},
move || Element::<'a, T>::from(text(error.to_owned()).small()),
)))
.padding(15)
.style(WarningStyle)
.width(Length::Fill)
}
pub struct WarningStyle;
impl container::StyleSheet for WarningStyle {
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::Appearance::default()
}
}
}
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())
}
}

View File

@ -1,24 +0,0 @@
use crate::ui::font;
use std::borrow::Cow;
pub const TEXT_REGULAR_SIZE: u16 = 25;
pub fn text<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(TEXT_REGULAR_SIZE)
}
pub trait Text {
fn bold(self) -> Self;
fn small(self) -> Self;
}
impl Text for iced::widget::Text<'_> {
fn bold(self) -> Self {
self.font(font::BOLD)
}
fn small(self) -> Self {
self.size(20)
}
}

View File

@ -1,36 +0,0 @@
use crate::ui::{color, icon};
use iced::widget::{self, Tooltip};
pub fn tooltip<'a, T: 'a>(help: &'static str) -> Tooltip<'a, T> {
Tooltip::new(
icon::tooltip_icon().style(color::DARK_GREY),
help,
widget::tooltip::Position::Right,
)
.style(TooltipStyle)
}
pub struct TooltipStyle;
impl widget::container::StyleSheet for TooltipStyle {
type Style = iced::Theme;
fn appearance(&self, _style: &Self::Style) -> widget::container::Appearance {
widget::container::Appearance {
border_radius: 10.0,
border_color: color::DARK_GREY,
border_width: 1.5,
background: color::FOREGROUND.into(),
..widget::container::Appearance::default()
}
}
}
impl From<TooltipStyle> for Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
fn from(s: TooltipStyle) -> Box<dyn widget::container::StyleSheet<Style = iced::Theme>> {
Box::new(s)
}
}
impl From<TooltipStyle> for iced::theme::Container {
fn from(i: TooltipStyle) -> iced::theme::Container {
iced::theme::Container::Custom(i.into())
}
}

View File

@ -1,11 +0,0 @@
use iced::Font;
pub const BOLD: Font = Font::External {
name: "Bold",
bytes: include_bytes!("../../static/fonts/OpenSans-Bold.ttf"),
};
pub const REGULAR: Font = Font::External {
name: "Regular",
bytes: include_bytes!("../../static/fonts/OpenSans-Regular.ttf"),
};

View File

@ -1,232 +0,0 @@
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<'static> {
Text::new(unicode.to_string())
.font(ICONS)
.width(Length::Units(20))
.horizontal_alignment(alignment::Horizontal::Center)
.size(20)
}
pub fn arrow_down() -> Text<'static> {
icon('\u{F128}')
}
pub fn chevron_right() -> Text<'static> {
icon('\u{F285}')
}
pub fn recovery_icon() -> Text<'static> {
icon('\u{F467}')
}
pub fn plug_icon() -> Text<'static> {
icon('\u{F4F6}')
}
pub fn reload_icon() -> Text<'static> {
icon('\u{F130}')
}
pub fn import_icon() -> Text<'static> {
icon('\u{F30A}')
}
pub fn wallet_icon() -> Text<'static> {
icon('\u{F615}')
}
pub fn hourglass_icon() -> Text<'static> {
icon('\u{F41F}')
}
pub fn hourglass_done_icon() -> Text<'static> {
icon('\u{F41E}')
}
pub fn vault_icon() -> Text<'static> {
icon('\u{F65A}')
}
pub fn bitcoin_icon() -> Text<'static> {
icon('\u{F635}')
}
pub fn history_icon() -> Text<'static> {
icon('\u{F292}')
}
pub fn home_icon() -> Text<'static> {
icon('\u{F3FC}')
}
pub fn unlock_icon() -> Text<'static> {
icon('\u{F600}')
}
pub fn warning_octagon_icon() -> Text<'static> {
icon('\u{F337}')
}
pub fn send_icon() -> Text<'static> {
icon('\u{F144}')
}
pub fn connect_device_icon() -> Text<'static> {
icon('\u{F348}')
}
pub fn connected_device_icon() -> Text<'static> {
icon('\u{F350}')
}
pub fn receive_icon() -> Text<'static> {
icon('\u{F123}')
}
pub fn calendar_icon() -> Text<'static> {
icon('\u{F1E8}')
}
pub fn turnback_icon() -> Text<'static> {
icon('\u{F131}')
}
pub fn vaults_icon() -> Text<'static> {
icon('\u{F1C7}')
}
pub fn coin_icon() -> Text<'static> {
icon('\u{F567}')
}
pub fn settings_icon() -> Text<'static> {
icon('\u{F3E5}')
}
pub fn block_icon() -> Text<'static> {
icon('\u{F1C8}')
}
pub fn square_icon() -> Text<'static> {
icon('\u{F584}')
}
pub fn square_check_icon() -> Text<'static> {
icon('\u{F26D}')
}
pub fn circle_check_icon() -> Text<'static> {
icon('\u{F26B}')
}
pub fn circle_cross_icon() -> Text<'static> {
icon('\u{F623}')
}
pub fn network_icon() -> Text<'static> {
icon('\u{F40D}')
}
pub fn dot_icon() -> Text<'static> {
icon('\u{F287}')
}
pub fn clipboard_icon() -> Text<'static> {
icon('\u{F3C2}')
}
pub fn shield_icon() -> Text<'static> {
icon('\u{F53F}')
}
pub fn shield_notif_icon() -> Text<'static> {
icon('\u{F530}')
}
pub fn shield_check_icon() -> Text<'static> {
icon('\u{F52F}')
}
pub fn person_check_icon() -> Text<'static> {
icon('\u{F4D6}')
}
pub fn person_icon() -> Text<'static> {
icon('\u{F4DA}')
}
pub fn tooltip_icon() -> Text<'static> {
icon('\u{F431}')
}
pub fn plus_icon() -> Text<'static> {
icon('\u{F4FE}')
}
pub fn warning_icon() -> Text<'static> {
icon('\u{F33B}')
}
pub fn chip_icon() -> Text<'static> {
icon('\u{F2D6}')
}
pub fn trash_icon() -> Text<'static> {
icon('\u{F5DE}')
}
pub fn key_icon() -> Text<'static> {
icon('\u{F44F}')
}
pub fn cross_icon() -> Text<'static> {
icon('\u{F62A}')
}
pub fn pencil_icon() -> Text<'static> {
icon('\u{F4CB}')
}
#[allow(dead_code)]
pub fn stakeholder_icon() -> Text<'static> {
icon('\u{F4AE}')
}
#[allow(dead_code)]
pub fn manager_icon() -> Text<'static> {
icon('\u{F4B4}')
}
pub fn done_icon() -> Text<'static> {
icon('\u{F26B}')
}
pub fn todo_icon() -> Text<'static> {
icon('\u{F28A}')
}
pub fn collapse_icon() -> Text<'static> {
icon('\u{F284}')
}
pub fn collapsed_icon() -> Text<'static> {
icon('\u{F282}')
}
pub fn down_icon() -> Text<'static> {
icon('\u{F279}')
}
pub fn up_icon() -> Text<'static> {
icon('\u{F27C}')
}
pub fn people_icon() -> Text<'static> {
icon('\u{F4CF}')
}

View File

@ -1,6 +0,0 @@
pub mod color;
/// component are wrappers around iced elements;
pub mod component;
pub mod font;
pub mod icon;
pub mod util;

View File

@ -1,28 +0,0 @@
/// from hecjr idea on Discord
use iced::{
widget::{Column, Row},
Element,
};
pub trait Collection<'a, Message>: Sized {
fn push(self, element: impl Into<Element<'a, Message>>) -> Self;
fn push_maybe(self, element: Option<impl Into<Element<'a, Message>>>) -> Self {
match element {
Some(element) => self.push(element),
None => self,
}
}
}
impl<'a, Message> Collection<'a, Message> for Column<'a, Message> {
fn push(self, element: impl Into<Element<'a, Message>>) -> Self {
Self::push(self, element)
}
}
impl<'a, Message> Collection<'a, Message> for Row<'a, Message> {
fn push(self, element: impl Into<Element<'a, Message>>) -> Self {
Self::push(self, element)
}
}

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2019-2020 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,6 +0,0 @@
# Icons
From the getbootstrap.com repository.
Converted from .woff to ttf with https://raw.githubusercontent.com/hanikesn/woff2otf/master/woff2otf.py
Use http://mathew-kurian.github.io/CharacterMap/ to check Unicode.

Binary file not shown.

View File

@ -9,8 +9,3 @@ edition = "2021"
iced = "0.7"
iced_native = "0.8"
iced_lazy = { version = "0.4"}
[workspace]
members = [
"examples/*"
]

View File

@ -5,23 +5,23 @@ use iced::{Alignment, Length};
use super::text::text;
pub fn alert<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Destructive.into())
button::Button::new(content(icon, t)).style(theme::Button::Destructive)
}
pub fn primary<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Primary.into())
button::Button::new(content(icon, t)).style(theme::Button::Primary)
}
pub fn transparent<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Transparent.into())
button::Button::new(content(icon, t)).style(theme::Button::Transparent)
}
pub fn border<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Secondary.into())
button::Button::new(content(icon, t)).style(theme::Button::Secondary)
}
pub fn transparent_border<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button(content(icon, t)).style(theme::Button::TransparentBorder.into())
button(content(icon, t)).style(theme::Button::TransparentBorder)
}
fn content<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Container<'a, T> {

View File

@ -2,10 +2,10 @@ use iced::Font;
pub const BOLD: Font = Font::External {
name: "Bold",
bytes: include_bytes!("../../static/fonts/OpenSans-Bold.ttf"),
bytes: include_bytes!("../static/fonts/OpenSans-Bold.ttf"),
};
pub const REGULAR: Font = Font::External {
name: "Regular",
bytes: include_bytes!("../../static/fonts/OpenSans-Regular.ttf"),
bytes: include_bytes!("../static/fonts/OpenSans-Regular.ttf"),
};

View File

@ -3,7 +3,7 @@ use iced::{alignment, Font, Length};
const ICONS: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../../static/icons/bootstrap-icons.ttf"),
bytes: include_bytes!("../static/icons/bootstrap-icons.ttf"),
};
fn icon(unicode: char) -> Text<'static> {

View File

@ -17,4 +17,7 @@ pub mod widget {
pub type Button<'a, Message> = iced::widget::Button<'a, Message, Renderer>;
pub type Text<'a> = iced::widget::Text<'a, Renderer>;
pub type Tooltip<'a> = iced::widget::Tooltip<'a, Renderer>;
pub type ProgressBar = iced::widget::ProgressBar<Renderer>;
pub type PickList<'a, Message> = iced::widget::PickList<'a, Message, Renderer>;
pub type Scrollable<'a, Message> = iced::widget::Scrollable<'a, Message, Renderer>;
}

View File

@ -1,15 +1,17 @@
use iced::{
application,
widget::{button, container, radio, text, text_input},
widget::{
button, checkbox, container, pick_list, progress_bar, radio, scrollable, text, text_input,
},
};
use super::color;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum Theme {
#[default]
Dark,
Light,
#[default]
Legacy,
}
@ -34,6 +36,32 @@ impl application::StyleSheet for Theme {
}
}
#[derive(Clone, Copy, Default)]
pub enum Overlay {
#[default]
Default,
}
impl iced::overlay::menu::StyleSheet for Theme {
type Style = Overlay;
fn appearance(&self, _style: &Self::Style) -> iced::overlay::menu::Appearance {
iced::overlay::menu::Appearance {
text_color: color::BLACK,
background: color::LIGHT_GREY.into(),
border_width: 1.0,
border_radius: 0.0,
border_color: color::GREEN,
selected_text_color: color::BLACK,
selected_background: color::GREEN.into(),
}
}
}
impl From<PickList> for Overlay {
fn from(_p: PickList) -> Overlay {
Overlay::Default
}
}
#[derive(Clone, Copy, Default)]
pub enum Text {
#[default]
@ -91,7 +119,7 @@ impl container::StyleSheet for Theme {
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::LIGHT_BLACK.into(),
border_color: color::LIGHT_BLACK,
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
@ -118,7 +146,7 @@ impl container::StyleSheet for Theme {
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::LIGHT_GREY.into(),
border_color: color::LIGHT_GREY,
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
@ -145,7 +173,7 @@ impl container::StyleSheet for Theme {
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::legacy::BORDER_GREY.into(),
border_color: color::legacy::BORDER_GREY,
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
@ -160,6 +188,24 @@ impl container::StyleSheet for Theme {
}
}
impl From<Card> for Container {
fn from(c: Card) -> Container {
Container::Card(c)
}
}
impl From<Badge> for Container {
fn from(c: Badge) -> Container {
Container::Badge(c)
}
}
impl From<Pill> for Container {
fn from(c: Pill) -> Container {
Container::Pill(c)
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum Card {
#[default]
@ -236,7 +282,6 @@ impl Card {
border_width: 1.0,
border_radius: 10.0,
border_color: color::legacy::ALERT,
..container::Appearance::default()
},
Card::Error => container::Appearance {
background: color::legacy::FOREGROUND.into(),
@ -244,7 +289,6 @@ impl Card {
border_width: 1.0,
border_radius: 10.0,
border_color: color::legacy::ALERT,
..container::Appearance::default()
},
Card::Warning => container::Appearance {
border_radius: 0.0,
@ -348,6 +392,80 @@ impl radio::StyleSheet for Theme {
}
}
#[derive(Default, Clone)]
pub struct Scrollable {}
impl scrollable::StyleSheet for Theme {
type Style = Scrollable;
fn active(&self, _style: &Self::Style) -> scrollable::Scrollbar {
scrollable::Scrollbar {
background: None,
border_width: 0.0,
border_color: color::legacy::BORDER_GREY,
border_radius: 10.0,
scroller: scrollable::Scroller {
color: color::legacy::BORDER_GREY,
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
},
}
}
fn hovered(&self, style: &Self::Style) -> scrollable::Scrollbar {
let active = self.active(style);
scrollable::Scrollbar { ..active }
}
}
#[derive(Default, Clone)]
pub enum PickList {
#[default]
Simple,
}
impl pick_list::StyleSheet for Theme {
type Style = PickList;
fn active(&self, _style: &Self::Style) -> pick_list::Appearance {
pick_list::Appearance {
placeholder_color: color::legacy::FOREGROUND,
handle_color: color::legacy::FOREGROUND,
background: color::legacy::FOREGROUND.into(),
border_width: 1.0,
border_color: color::legacy::BORDER_GREY,
border_radius: 10.0,
text_color: iced::Color::BLACK,
}
}
fn hovered(&self, style: &Self::Style) -> pick_list::Appearance {
let active = self.active(style);
pick_list::Appearance { ..active }
}
}
#[derive(Default)]
pub struct CheckBox {}
impl checkbox::StyleSheet for Theme {
type Style = CheckBox;
fn active(&self, _style: &Self::Style, _is_selected: bool) -> checkbox::Appearance {
checkbox::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::GREY,
checkmark_color: color::GREEN,
text_color: None,
border_radius: 0.0,
}
}
fn hovered(&self, style: &Self::Style, is_selected: bool) -> checkbox::Appearance {
let active = self.active(style, is_selected);
checkbox::Appearance { ..active }
}
}
#[derive(Default)]
pub enum Button {
#[default]
@ -644,3 +762,20 @@ impl text_input::StyleSheet for Theme {
iced::Color::from_rgb(0.8, 0.8, 1.0)
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum ProgressBar {
#[default]
Simple,
}
impl progress_bar::StyleSheet for Theme {
type Style = ProgressBar;
fn appearance(&self, _style: &Self::Style) -> progress_bar::Appearance {
progress_bar::Appearance {
background: iced::Color::TRANSPARENT.into(),
bar: color::GREEN.into(),
border_radius: 10.0,
}
}
}