Merge #372: gui: design system example

d4d5a66aa1f7323398267ee80bbf98bec2284698 gui: transitioning to ui crate (edouard)
8d337eb7e183b44f967a1e7f19c23141ffef6237 ui: add support for legacy (edouard)
f3bf6fa589ed23ded4633de1ea9d80d9ab04e8f4 gui: design system example (edouard)

Pull request description:

  This PR reorganizes the ui module in its own crate and makes usage of the iced theming feature.
  The new theme that is introduced help to keep track of the colors and appearance of each widget, it is a crucial part for the next-coming design system and it is a `iced` good practice that since Iced 0.7 we must take care.
  This PR also put the basics for a little wasm app example that will help us to share and review new components design.

ACKs for top commit:
  edouardparis:
    Self-ACK d4d5a66aa1f7323398267ee80bbf98bec2284698

Tree-SHA512: 9d01a2c734c1880da5115aeec5632b649f9b81bb963ab72f0772ee06705805e4df3ab294dff577765bf76b420017faf51d3a5b67e0e1dffadcd8c7d193593241
This commit is contained in:
edouard 2023-03-28 10:26:00 +02:00
commit 30054d2755
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
79 changed files with 1900 additions and 1180 deletions

3
.gitignore vendored
View File

@ -8,3 +8,6 @@ venv/
pytest.log
TODO
**/target
**/dist
gui/ui/Cargo.lock
gui/ui/examples/design-system/Cargo.lock

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,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,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,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,6 +0,0 @@
pub mod color;
/// component are wrappers around iced elements;
pub mod component;
pub mod font;
pub mod icon;
pub mod util;

11
gui/ui/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "liana_ui"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = "0.7"
iced_native = "0.8"
iced_lazy = { version = "0.4"}

6
gui/ui/README.md Normal file
View File

@ -0,0 +1,6 @@
# `liana_ui`
UI components used across the liana GUI iced application.
An wasm compatible example of the design system can be found in
`./examples/design-system`

View File

@ -0,0 +1,12 @@
[package]
name = "design-system"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = "0.7"
iced_native = "0.8"
web-sys = "0.3.61"
liana_ui = { path = "../.." }

View File

@ -0,0 +1,7 @@
## Use WASM
`trunk serve`
to build for a directory on a distant server
`trunk build --public-url /design-system`

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Liana design system</title>
<base data-trunk-public-url />
<style>
body {
margin:0px;
overflow:hidden;
}
</style>
</head>
<body>
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="design-system" />
</body>
</html>

View File

@ -0,0 +1,175 @@
mod section;
use iced::widget::{button, column, container, radio, row, text, Space};
use iced::{executor, Application, Command, Length, Settings, Subscription};
use liana_ui::{theme, widget::*};
pub fn main() -> iced::Result {
DesignSystem::run(Settings::with_flags(Config {}))
}
struct Config {}
#[derive(Default)]
struct DesignSystem {
theme: theme::Theme,
sections: Vec<Box<dyn Section>>,
current: usize,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ThemeType {
Light,
Dark,
Legacy,
}
#[derive(Debug, Clone)]
pub enum Message {
ThemeChanged(ThemeType),
Event(iced_native::Event),
Section(usize),
Ignore,
}
impl Application for DesignSystem {
type Message = Message;
type Theme = theme::Theme;
type Flags = Config;
type Executor = executor::Default;
fn title(&self) -> String {
String::from("Liana - Design System")
}
fn new(_config: Config) -> (Self, Command<Self::Message>) {
let app = Self {
theme: theme::Theme::Light,
sections: vec![
Box::new(section::Overview {}),
Box::new(section::Colors {}),
Box::new(section::Buttons {}),
],
current: 0,
};
#[cfg(target_arch = "wasm32")]
{
use iced_native::{command, window};
let window = web_sys::window().unwrap();
let (width, height) = (
(window.inner_width().unwrap().as_f64().unwrap()) as u32,
(window.inner_height().unwrap().as_f64().unwrap()) as u32,
);
(
app,
Command::single(command::Action::Window(window::Action::Resize {
width,
height,
})),
)
}
#[cfg(not(target_arch = "wasm32"))]
(app, Command::none())
}
fn update(&mut self, message: Message) -> Command<Self::Message> {
match message {
Message::ThemeChanged(theme) => {
self.theme = match theme {
ThemeType::Light => theme::Theme::Light,
ThemeType::Dark => theme::Theme::Dark,
ThemeType::Legacy => theme::Theme::Legacy,
}
}
Message::Section(i) => {
if self.sections.get(i).is_some() {
self.current = i;
}
}
Message::Event(iced::Event::Window(iced_native::window::Event::Resized {
width,
height,
})) => {
#[cfg(target_arch = "wasm32")]
{
use iced_native::{command, window};
return Command::single(command::Action::Window(window::Action::Resize {
width,
height,
}));
}
}
_ => {}
}
Command::none()
}
fn subscription(&self) -> Subscription<Self::Message> {
iced_native::subscription::events().map(Self::Message::Event)
}
fn view(&self) -> Element<Message> {
let sidebar = container(
column![
[ThemeType::Light, ThemeType::Dark].iter().fold(
column![text("Choose a theme:")].spacing(10),
|column, theme| {
column.push(radio(
format!("{theme:?}"),
*theme,
Some(match self.theme {
theme::Theme::Light => ThemeType::Light,
theme::Theme::Dark => ThemeType::Dark,
theme::Theme::Legacy => ThemeType::Legacy,
}),
Message::ThemeChanged,
))
},
),
Space::with_height(Length::Units(100)),
self.sections.iter().enumerate().fold(
Column::new().spacing(10),
|col, (i, section)| col.push(
button(
container(text(section.title()))
.width(Length::Fill)
.padding(5)
)
.style(if i == self.current {
theme::Button::Primary
} else {
theme::Button::Transparent
})
.on_press(Message::Section(i))
.width(Length::Units(200))
)
)
]
.spacing(20),
)
.padding(20)
.style(theme::Container::Foreground)
.height(Length::Fill);
container(row![
sidebar.width(Length::Units(200)),
Space::with_width(Length::Units(150)),
column![
Space::with_height(Length::Units(150)),
container(self.sections[self.current].view()).width(Length::Fill)
]
])
.width(Length::Fill)
.height(Length::Fill)
.into()
}
fn theme(&self) -> theme::Theme {
self.theme.clone()
}
}
pub trait Section {
fn title(&self) -> &'static str;
fn view(&self) -> Element<Message>;
}

View File

@ -0,0 +1,104 @@
use iced::{
alignment,
widget::{button, column, container, row, Space},
Alignment, Length,
};
use liana_ui::{color, component::text::*, theme, widget::Element};
use super::{Message, Section};
pub struct Overview {}
impl Section for Overview {
fn title(&self) -> &'static str {
"Overview"
}
fn view(&self) -> Element<Message> {
column![
text("Hello").bold().size(50),
column![text(
"This is the Liana design system for the Iced framework"
)]
.spacing(10)
]
.spacing(100)
.into()
}
}
pub struct Colors {}
impl Section for Colors {
fn title(&self) -> &'static str {
"Colors"
}
fn view(&self) -> Element<Message> {
column![
text(self.title()).bold().size(50),
column![
color_row(color::BLACK, "BLACK (0,0,0)"),
color_row(color::LIGHT_BLACK, "LIGHT_BLACK #141414 original design"),
color_row(color::GREEN, "GREEN #00FF66 original design"),
color_row(color::DARK_GREY, "DARK_GREY #555555"),
color_row(color::GREY, "GREY #CCCCCC original design"),
color_row(color::LIGHT_GREY, "LIGHT_GREY #E6E6E6 original design"),
color_row(color::RED, "RED #F04359"),
color_row(color::ORANGE, "ORANGE #FFa700")
]
.spacing(10)
]
.spacing(100)
.into()
}
}
fn color_row<'a, T: 'a>(color: iced::Color, label: &'static str) -> Element<'a, T> {
row![
container(Space::with_width(Length::Units(100)))
.height(Length::Units(100))
.style(theme::Container::Custom(color)),
text(label)
]
.spacing(10)
.align_items(Alignment::Center)
.spacing(10)
.into()
}
pub struct Buttons {}
impl Section for Buttons {
fn title(&self) -> &'static str {
"Buttons"
}
fn view(&self) -> Element<Message> {
column![
text(self.title()).bold().size(50),
column![
button_row(theme::Button::Primary, "Primary"),
button_row(theme::Button::Secondary, "Secondary"),
button_row(theme::Button::Destructive, "Destructive"),
button_row(theme::Button::Transparent, "Transparent")
]
.spacing(20)
]
.spacing(100)
.into()
}
}
fn button_row(style: theme::Button, label: &'static str) -> Element<Message> {
button(
container(text(label))
.width(Length::Fill)
.align_x(alignment::Horizontal::Center),
)
.width(Length::Units(200))
.padding(5)
.style(style)
.on_press(Message::Ignore)
.into()
}

126
gui/ui/src/color.rs Normal file
View File

@ -0,0 +1,126 @@
use iced::Color;
pub const BLACK: Color = iced::Color::BLACK;
pub const LIGHT_BLACK: Color = Color::from_rgb(
0x14 as f32 / 255.0,
0x14 as f32 / 255.0,
0x14 as f32 / 255.0,
);
pub const GREEN: Color = Color::from_rgb(
0x00 as f32 / 255.0,
0xFF as f32 / 255.0,
0x66 as f32 / 255.0,
);
pub const DARK_GREY: Color = Color::from_rgb(
0x55 as f32 / 255.0,
0x55 as f32 / 255.0,
0x55 as f32 / 255.0,
);
pub const GREY: Color = Color::from_rgb(
0xCC as f32 / 255.0,
0xCC as f32 / 255.0,
0xCC as f32 / 255.0,
);
pub const LIGHT_GREY: Color = Color::from_rgb(
0xE6 as f32 / 255.0,
0xE6 as f32 / 255.0,
0xE6 as f32 / 255.0,
);
pub const RED: Color = Color::from_rgb(
0xF0 as f32 / 255.0,
0x43 as f32 / 255.0,
0x59 as f32 / 255.0,
);
pub const ORANGE: Color =
Color::from_rgb(0xFF as f32 / 255.0, 0xa7 as f32 / 255.0, 0x0 as f32 / 255.0);
pub mod legacy {
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

@ -0,0 +1,101 @@
use iced::{widget::tooltip, Length};
use crate::{component::text::*, icon, theme, widget::*};
pub struct Badge {
icon: crate::widget::Text<'static>,
style: theme::Badge,
}
impl Badge {
pub fn new(icon: crate::widget::Text<'static>) -> Self {
Self {
icon,
style: theme::Badge::Standard,
}
}
pub fn style(self, style: theme::Badge) -> 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(theme::Container::Badge(badge.style))
.center_x()
.center_y()
.into()
}
}
pub fn receive<T>() -> Container<'static, T> {
Container::new(icon::receive_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(theme::Container::Badge(theme::Badge::Standard))
.center_x()
.center_y()
}
pub fn spend<T>() -> Container<'static, T> {
Container::new(icon::send_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(theme::Container::Badge(theme::Badge::Standard))
.center_x()
.center_y()
}
pub fn coin<T>() -> Container<'static, T> {
Container::new(icon::coin_icon().width(Length::Units(20)))
.width(Length::Units(40))
.height(Length::Units(40))
.style(theme::Container::Badge(theme::Badge::Standard))
.center_x()
.center_y()
}
pub fn unconfirmed<'a, T: 'a>() -> Container<'a, T> {
Container::new(
tooltip::Tooltip::new(
Container::new(text(" Unconfirmed ").small())
.padding(3)
.style(theme::Container::Pill(theme::Pill::Simple)),
"Do not treat this as a payment until it is confirmed",
tooltip::Position::Top,
)
.style(theme::Container::Card(theme::Card::Simple)),
)
}
pub fn deprecated<'a, T: 'a>() -> Container<'a, T> {
Container::new(
tooltip::Tooltip::new(
Container::new(text(" Deprecated ").small())
.padding(3)
.style(theme::Container::Pill(theme::Pill::Simple)),
"This spend cannot be included anymore in the blockchain",
tooltip::Position::Top,
)
.style(theme::Container::Card(theme::Card::Simple)),
)
}
pub fn spent<'a, T: 'a>() -> Container<'a, T> {
Container::new(
tooltip::Tooltip::new(
Container::new(text(" Spent ").small())
.padding(3)
.style(theme::Container::Pill(theme::Pill::Simple)),
"The spend transaction was included in the blockchain",
tooltip::Position::Top,
)
.style(theme::Container::Card(theme::Card::Simple)),
)
}

View File

@ -0,0 +1,40 @@
use crate::{theme, widget::*};
use iced::widget::{button, container, row};
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)
}
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)
}
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)
}
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)
}
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)
}
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![i, text(t)]
.spacing(10)
.width(iced::Length::Fill)
.align_items(Alignment::Center),
)
.width(iced::Length::Fill)
.center_x()
.padding(5),
}
}

View File

@ -0,0 +1,44 @@
use crate::{color, component::text::text, icon, theme, widget::*};
pub fn simple<'a, T: 'a, C: Into<Element<'a, T>>>(content: C) -> Container<'a, T> {
Container::new(content)
.padding(15)
.style(theme::Container::Card(theme::Card::Simple))
}
pub fn invalid<'a, T: 'a, C: Into<Element<'a, T>>>(content: C) -> Container<'a, T> {
Container::new(content)
.padding(15)
.style(theme::Container::Card(theme::Card::Invalid))
}
/// display an error card with the message and the error in a tooltip.
pub fn warning<'a, T: 'a>(message: String) -> Container<'a, T> {
Container::new(
Row::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.push(icon::warning_octagon_icon().style(color::legacy::WARNING))
.push(text(message).style(color::legacy::WARNING)),
)
.padding(15)
.style(theme::Container::Card(theme::Card::Warning))
}
/// display an error card with the message and the error in a tooltip.
pub fn error<'a, T: 'a>(message: &'static str, error: String) -> Container<'a, T> {
Container::new(
iced::widget::tooltip::Tooltip::new(
Row::new()
.spacing(20)
.align_items(iced::Alignment::Center)
.push(icon::warning_icon().style(color::legacy::ALERT))
.push(text(message).style(color::legacy::ALERT)),
error,
iced::widget::tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Error)),
)
.padding(15)
.style(theme::Container::Card(theme::Card::Error))
}

View File

@ -1,7 +1,5 @@
use iced::{
widget::{Button, Column},
Element,
};
use crate::widget::*;
use iced::widget::column;
use iced_lazy::{self, Component};
use std::marker::PhantomData;
@ -36,7 +34,8 @@ pub enum Event<T> {
Collapse(bool),
}
impl<'a, Message, T, H, F, C> Component<Message, iced::Renderer> for Collapse<'a, Message, H, F, C>
impl<'a, Message, T, H, F, C> Component<Message, iced::Renderer<crate::theme::Theme>>
for Collapse<'a, Message, H, F, C>
where
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>>,
@ -58,14 +57,13 @@ where
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()
column![
(self.after)().on_press(Event::Collapse(false)),
(self.content)().map(Event::Internal)
]
.into()
} else {
Column::new()
.push((self.before)().on_press(Event::Collapse(true)))
.into()
column![(self.before)().on_press(Event::Collapse(true))].into()
}
}
}
@ -75,9 +73,9 @@ impl<'a, Message, T, H: 'a, F: 'a, C: 'a> From<Collapse<'a, Message, H, F, C>>
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>,
H: Fn() -> Button<'a, Event<T>>,
F: Fn() -> Button<'a, Event<T>>,
C: Fn() -> Element<'a, T>,
{
fn from(c: Collapse<'a, Message, H, F, C>) -> Self {
iced_lazy::component(c)

View File

@ -1,12 +1,6 @@
use iced::{
widget::{
text_input::{Appearance, StyleSheet, TextInput},
Column, Container,
},
Element, Length,
};
use iced::{widget::text_input, Length};
use crate::ui::{color, component::text::*, util::Collection};
use crate::{color, component::text::*, theme, util::Collection, widget::*};
#[derive(Debug, Clone)]
pub struct Value<T> {
@ -24,7 +18,7 @@ impl std::default::Default for Value<String> {
}
pub struct Form<'a, Message> {
input: TextInput<'a, Message>,
input: text_input::TextInput<'a, Message, iced::Renderer<theme::Theme>>,
warning: Option<&'a str>,
valid: bool,
}
@ -44,7 +38,7 @@ where
F: 'static + Fn(String) -> Message,
{
Self {
input: TextInput::new(placeholder, &value.value, on_change),
input: text_input::TextInput::new(placeholder, &value.value, on_change),
warning: None,
valid: value.valid,
}
@ -74,13 +68,13 @@ impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
Container::new(
Column::new()
.push(if !form.valid {
form.input.style(InvalidFormStyle)
form.input.style(theme::Form::Invalid)
} else {
form.input
})
.push_maybe(if !form.valid {
form.warning
.map(|message| text(message).style(color::ALERT).small())
.map(|message| text(message).style(color::legacy::ALERT).small())
} else {
None
})
@ -91,47 +85,3 @@ impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
.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

@ -0,0 +1,21 @@
pub mod badge;
pub mod button;
pub mod card;
pub mod collapse;
pub mod form;
pub mod modal;
pub mod notification;
pub mod text;
pub mod tooltip;
pub use tooltip::tooltip;
use iced::Length;
use crate::{theme, widget::*};
pub fn separation<'a, T: 'a>() -> Container<'a, T> {
Container::new(Column::new().push(Text::new(" ")))
.style(theme::Container::Border)
.height(Length::Units(1))
}

View File

@ -1,12 +1,9 @@
use crate::ui::{
color,
component::{button, collapse, text::*},
icon,
};
use iced::{
widget::{container, Button, Container, Row},
Alignment, Element, Length,
use crate::{
component::{collapse, text::*},
icon, theme,
widget::*,
};
use iced::{Alignment, Length};
pub fn warning<'a, T: 'a + Clone>(message: String, error: String) -> Container<'a, T> {
let message_clone = message.clone();
@ -26,7 +23,7 @@ pub fn warning<'a, T: 'a + Clone>(message: String, error: String) -> Container<'
.push(icon::collapse_icon()),
),
)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
},
move || {
Button::new(
@ -42,37 +39,11 @@ pub fn warning<'a, T: 'a + Clone>(message: String, error: String) -> Container<'
.push(icon::collapsed_icon()),
),
)
.style(button::Style::Transparent.into())
.style(theme::Button::Transparent)
},
move || Element::<'a, T>::from(text(error.to_owned()).small()),
)))
.padding(15)
.style(WarningStyle)
.style(theme::Container::Card(theme::Card::Warning))
.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,9 +1,9 @@
use crate::ui::font;
use crate::{font, theme::Theme};
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> {
pub fn text<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(TEXT_REGULAR_SIZE)
@ -14,7 +14,7 @@ pub trait Text {
fn small(self) -> Self;
}
impl Text for iced::widget::Text<'_> {
impl Text for iced::widget::Text<'_, iced::Renderer<Theme>> {
fn bold(self) -> Self {
self.font(font::BOLD)
}

View File

@ -0,0 +1,12 @@
use crate::{icon, theme, widget::*};
pub fn tooltip<'a, T: 'a>(help: &'static str) -> Container<'a, T> {
Container::new(
iced::widget::tooltip::Tooltip::new(
icon::tooltip_icon(),
help,
iced::widget::tooltip::Position::Right,
)
.style(theme::Container::Card(theme::Card::Simple)),
)
}

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

@ -1,8 +1,9 @@
use iced::{alignment, widget::Text, Font, Length};
use crate::widget::*;
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> {

23
gui/ui/src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
pub mod color;
pub mod component;
pub mod font;
pub mod icon;
pub mod theme;
pub mod util;
pub mod widget {
#![allow(dead_code)]
use crate::theme::Theme;
pub type Renderer = iced::Renderer<Theme>;
pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>;
pub type Container<'a, Message> = iced::widget::Container<'a, Message, Renderer>;
pub type Column<'a, Message> = iced::widget::Column<'a, Message, Renderer>;
pub type Row<'a, Message> = iced::widget::Row<'a, Message, Renderer>;
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>;
}

781
gui/ui/src/theme.rs Normal file
View File

@ -0,0 +1,781 @@
use iced::{
application,
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 {
Dark,
Light,
#[default]
Legacy,
}
impl application::StyleSheet for Theme {
type Style = ();
fn appearance(&self, _style: &Self::Style) -> application::Appearance {
match self {
Theme::Light => application::Appearance {
background_color: color::LIGHT_GREY,
text_color: color::LIGHT_BLACK,
},
Theme::Dark => application::Appearance {
background_color: color::LIGHT_BLACK,
text_color: color::LIGHT_GREY,
},
Theme::Legacy => application::Appearance {
background_color: color::legacy::BACKGROUND,
text_color: color::BLACK,
},
}
}
}
#[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]
Default,
Color(iced::Color),
}
impl From<iced::Color> for Text {
fn from(color: iced::Color) -> Self {
Text::Color(color)
}
}
impl text::StyleSheet for Theme {
type Style = Text;
fn appearance(&self, style: Self::Style) -> text::Appearance {
match style {
Text::Default => Default::default(),
Text::Color(c) => text::Appearance { color: Some(c) },
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum Container {
#[default]
Transparent,
Background,
Foreground,
Border,
Card(Card),
Badge(Badge),
Pill(Pill),
Custom(iced::Color),
}
impl container::StyleSheet for Theme {
type Style = Container;
fn appearance(&self, style: &Self::Style) -> iced::widget::container::Appearance {
match self {
Theme::Light => match style {
Container::Transparent => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
..container::Appearance::default()
},
Container::Background => container::Appearance {
background: color::LIGHT_GREY.into(),
..container::Appearance::default()
},
Container::Foreground => container::Appearance {
background: color::GREY.into(),
..container::Appearance::default()
},
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::LIGHT_BLACK,
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
Container::Badge(c) => c.appearance(self),
Container::Pill(c) => c.appearance(self),
Container::Custom(c) => container::Appearance {
background: (*c).into(),
..container::Appearance::default()
},
},
Theme::Dark => match style {
Container::Transparent => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
..container::Appearance::default()
},
Container::Background => container::Appearance {
background: color::LIGHT_BLACK.into(),
..container::Appearance::default()
},
Container::Foreground => container::Appearance {
background: color::BLACK.into(),
..container::Appearance::default()
},
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::LIGHT_GREY,
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
Container::Badge(c) => c.appearance(self),
Container::Pill(c) => c.appearance(self),
Container::Custom(c) => container::Appearance {
background: (*c).into(),
..container::Appearance::default()
},
},
Theme::Legacy => match style {
Container::Transparent => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
..container::Appearance::default()
},
Container::Background => container::Appearance {
background: color::legacy::BACKGROUND.into(),
..container::Appearance::default()
},
Container::Foreground => container::Appearance {
background: color::legacy::FOREGROUND.into(),
..container::Appearance::default()
},
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::legacy::BORDER_GREY,
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
Container::Badge(c) => c.appearance(self),
Container::Pill(c) => c.appearance(self),
Container::Custom(c) => container::Appearance {
background: (*c).into(),
..container::Appearance::default()
},
},
}
}
}
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]
Simple,
Invalid,
Warning,
Error,
}
impl Card {
fn appearance(&self, theme: &Theme) -> iced::widget::container::Appearance {
match theme {
Theme::Light => match self {
Card::Simple => container::Appearance {
background: color::GREY.into(),
..container::Appearance::default()
},
Card::Invalid => container::Appearance {
background: color::GREY.into(),
text_color: color::BLACK.into(),
border_width: 1.0,
border_color: color::RED,
..container::Appearance::default()
},
Card::Error => container::Appearance {
background: color::GREY.into(),
text_color: color::RED.into(),
border_width: 1.0,
border_color: color::RED,
..container::Appearance::default()
},
Card::Warning => container::Appearance {
background: color::ORANGE.into(),
text_color: color::GREY.into(),
..container::Appearance::default()
},
},
Theme::Dark => match self {
Card::Simple => container::Appearance {
background: color::LIGHT_BLACK.into(),
..container::Appearance::default()
},
Card::Invalid => container::Appearance {
background: color::LIGHT_BLACK.into(),
text_color: color::BLACK.into(),
border_width: 1.0,
border_color: color::RED,
..container::Appearance::default()
},
Card::Error => container::Appearance {
background: color::LIGHT_BLACK.into(),
text_color: color::RED.into(),
border_width: 1.0,
border_color: color::RED,
..container::Appearance::default()
},
Card::Warning => container::Appearance {
background: color::ORANGE.into(),
text_color: color::GREY.into(),
..container::Appearance::default()
},
},
Theme::Legacy => match self {
Card::Simple => container::Appearance {
background: color::legacy::FOREGROUND.into(),
border_radius: 10.0,
border_color: color::legacy::BORDER_GREY,
border_width: 1.0,
..container::Appearance::default()
},
Card::Invalid => container::Appearance {
background: color::legacy::FOREGROUND.into(),
text_color: iced::Color::BLACK.into(),
border_width: 1.0,
border_radius: 10.0,
border_color: color::legacy::ALERT,
},
Card::Error => container::Appearance {
background: color::legacy::FOREGROUND.into(),
text_color: color::legacy::ALERT.into(),
border_width: 1.0,
border_radius: 10.0,
border_color: color::legacy::ALERT,
},
Card::Warning => container::Appearance {
border_radius: 0.0,
text_color: iced::Color::BLACK.into(),
background: color::legacy::WARNING.into(),
border_color: color::legacy::WARNING,
..container::Appearance::default()
},
},
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum Badge {
#[default]
Standard,
Bitcoin,
}
impl Badge {
fn appearance(&self, _theme: &Theme) -> iced::widget::container::Appearance {
match self {
Self::Standard => container::Appearance {
border_radius: 40.0,
background: color::legacy::BACKGROUND.into(),
..container::Appearance::default()
},
Self::Bitcoin => container::Appearance {
border_radius: 40.0,
background: color::legacy::WARNING.into(),
text_color: iced::Color::WHITE.into(),
..container::Appearance::default()
},
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum Pill {
#[default]
Simple,
InversePrimary,
Primary,
Success,
}
impl Pill {
fn appearance(&self, _theme: &Theme) -> iced::widget::container::Appearance {
match self {
Self::Primary => container::Appearance {
background: color::legacy::PRIMARY.into(),
border_radius: 10.0,
text_color: iced::Color::WHITE.into(),
..container::Appearance::default()
},
Self::InversePrimary => container::Appearance {
background: color::legacy::FOREGROUND.into(),
border_radius: 10.0,
text_color: color::legacy::PRIMARY.into(),
..container::Appearance::default()
},
Self::Success => container::Appearance {
background: color::legacy::SUCCESS.into(),
border_radius: 10.0,
text_color: iced::Color::WHITE.into(),
..container::Appearance::default()
},
Self::Simple => container::Appearance {
background: color::legacy::BACKGROUND.into(),
border_radius: 10.0,
text_color: iced::Color::BLACK.into(),
..container::Appearance::default()
},
}
}
}
#[derive(Default)]
pub struct Radio {}
impl radio::StyleSheet for Theme {
type Style = Radio;
fn active(&self, _style: &Self::Style, _is_selected: bool) -> radio::Appearance {
radio::Appearance {
background: iced::Color::TRANSPARENT.into(),
dot_color: color::GREEN,
border_width: 1.0,
border_color: color::GREEN,
text_color: None,
}
}
fn hovered(&self, style: &Self::Style, is_selected: bool) -> radio::Appearance {
let active = self.active(style, is_selected);
radio::Appearance {
dot_color: color::GREEN,
background: iced::Color::TRANSPARENT.into(),
..active
}
}
}
#[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]
Primary,
Secondary,
Destructive,
Transparent,
TransparentBorder,
}
impl button::StyleSheet for Theme {
type Style = Button;
fn active(&self, style: &Self::Style) -> button::Appearance {
match self {
Theme::Light => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::LIGHT_BLACK.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: color::DARK_GREY,
text_color: color::LIGHT_BLACK,
},
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::RED.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::Transparent | Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
},
Theme::Dark => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::GREY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::LIGHT_BLACK.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: color::LIGHT_GREY,
text_color: color::LIGHT_GREY,
},
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::RED.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
Button::Transparent | Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
},
Theme::Legacy => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::legacy::PRIMARY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::legacy::FOREGROUND,
},
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::legacy::FOREGROUND.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: color::legacy::ALERT,
text_color: color::legacy::ALERT,
},
Button::Transparent | Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: iced::Color::BLACK,
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.2,
border_color: color::legacy::BORDER_GREY,
text_color: iced::Color::BLACK,
},
},
}
}
fn hovered(&self, style: &Self::Style) -> button::Appearance {
match self {
Theme::Light => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::LIGHT_BLACK.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::LIGHT_BLACK.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::RED.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::Transparent => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::DARK_GREY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::DARK_GREY.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: color::LIGHT_BLACK,
text_color: color::LIGHT_GREY,
},
},
Theme::Dark => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::GREY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::GREY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::RED.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
Button::Transparent => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::DARK_GREY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::DARK_GREY.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: color::LIGHT_GREY,
text_color: color::LIGHT_GREY,
},
},
Theme::Legacy => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::legacy::PRIMARY.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: color::legacy::FOREGROUND,
},
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::legacy::FOREGROUND.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: color::legacy::ALERT,
text_color: color::legacy::ALERT,
},
Button::Transparent => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
text_color: iced::Color::BLACK,
},
Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: iced::Color::BLACK,
text_color: iced::Color::BLACK,
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_width: 1.0,
border_color: iced::Color::BLACK,
text_color: iced::Color::BLACK,
},
},
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum Form {
#[default]
Simple,
Invalid,
}
impl text_input::StyleSheet for Theme {
type Style = Form;
fn active(&self, style: &Self::Style) -> text_input::Appearance {
match style {
Form::Simple => text_input::Appearance {
background: iced::Background::Color(color::legacy::FOREGROUND),
border_radius: 5.0,
border_width: 1.0,
border_color: color::legacy::DARK_GREY,
},
Form::Invalid => text_input::Appearance {
background: iced::Background::Color(color::legacy::FOREGROUND),
border_radius: 5.0,
border_width: 1.0,
border_color: color::legacy::ALERT,
},
}
}
fn focused(&self, style: &Self::Style) -> text_input::Appearance {
text_input::Appearance {
..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)
}
}
#[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,
}
}
}

View File

@ -1,8 +1,5 @@
/// from hecjr idea on Discord
use iced::{
widget::{Column, Row},
Element,
};
use crate::widget::*;
pub trait Collection<'a, Message>: Sized {
fn push(self, element: impl Into<Element<'a, Message>>) -> Self;