diff --git a/.gitignore b/.gitignore index aee93bf9..a58ff790 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ venv/ pytest.log TODO **/target +**/dist +gui/ui/Cargo.lock +gui/ui/examples/design-system/Cargo.lock diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 1aaeb986..064ff420 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -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" diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 34d16347..084f5668 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -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"] diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 1d31edca..5160edc1 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -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; diff --git a/gui/src/app/state/coins.rs b/gui/src/app/state/coins.rs index dad584df..83119eca 100644 --- a/gui/src/app/state/coins.rs +++ b/gui/src/app/state/coins.rs @@ -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}, diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index b95dea4d..9ffd6b17 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -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}; diff --git a/gui/src/app/state/recovery.rs b/gui/src/app/state/recovery.rs index cd0d7dcb..f6d9ac32 100644 --- a/gui/src/app/state/recovery.rs +++ b/gui/src/app/state/recovery.rs @@ -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}; diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index de5fe3e1..1d7d2431 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -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)] diff --git a/gui/src/app/state/settings/mod.rs b/gui/src/app/state/settings/mod.rs index 1dfe931b..471a7e00 100644 --- a/gui/src/app/state/settings/mod.rs +++ b/gui/src/app/state/settings/mod.rs @@ -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; diff --git a/gui/src/app/state/settings/wallet.rs b/gui/src/app/state/settings/wallet.rs index 10149449..90d78cf3 100644 --- a/gui/src/app/state/settings/wallet.rs +++ b/gui/src/app/state/settings/wallet.rs @@ -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 { diff --git a/gui/src/app/state/spend/detail.rs b/gui/src/app/state/spend/detail.rs index 1c40fe98..4924bfa9 100644 --- a/gui/src/app/state/spend/detail.rs +++ b/gui/src/app/state/spend/detail.rs @@ -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 { diff --git a/gui/src/app/state/spend/mod.rs b/gui/src/app/state/spend/mod.rs index 230167f5..771a9e88 100644 --- a/gui/src/app/state/spend/mod.rs +++ b/gui/src/app/state/spend/mod.rs @@ -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 { diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 9ac59f3c..5289c57c 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -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 diff --git a/gui/src/app/view/coins.rs b/gui/src/app/view/coins.rs index fb1ede02..15e7a00f 100644 --- a/gui/src/app/view/coins.rs +++ b/gui/src/app/view/coins.rs @@ -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)) } diff --git a/gui/src/app/view/home.rs b/gui/src/app/view/home.rs index e55b1ddd..7b4a30a2 100644 --- a/gui/src/app/view/home.rs +++ b/gui/src/app/view/home.rs @@ -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), ), diff --git a/gui/src/app/view/hw.rs b/gui/src/app/view/hw.rs index 064c5e8c..8ee703b9 100644 --- a/gui/src/app/view/hw.rs +++ b/gui/src/app/view/hw.rs @@ -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() } diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 7baf0cec..bd620e92 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -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>>( @@ -268,9 +267,9 @@ pub fn dashboard<'a, T: Into>>( .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>, F: Into>>( .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>, F: Into>>( .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) diff --git a/gui/src/app/view/receive.rs b/gui/src/app/view/receive.rs index 7e6a9706..fd78747c 100644 --- a/gui/src/app/view/receive.rs +++ b/gui/src/app/view/receive.rs @@ -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), ) diff --git a/gui/src/app/view/recovery.rs b/gui/src/app/view/recovery.rs index c4367e7f..b03e40e5 100644 --- a/gui/src/app/view/recovery.rs +++ b/gui/src/app/view/recovery.rs @@ -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), diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 5cd64f3d..c1e014fd 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -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 { @@ -37,7 +35,7 @@ pub fn list(cache: &Cache) -> Element { .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 { .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 { .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 { .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 { .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) -> widget::Container<'a, T> { +pub fn is_running_label<'a, T: 'a>(is_running: Option) -> 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 diff --git a/gui/src/app/view/spend/detail.rs b/gui/src/app/view/spend/detail.rs index df9992e5..95e28429 100644 --- a/gui/src/app/view/spend/detail.rs +++ b/gui/src/app/view/spend/detail.rs @@ -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>>( }), ) .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::>::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), ) diff --git a/gui/src/app/view/spend/mod.rs b/gui/src/app/view/spend/mod.rs index 27e121de..465d2e60 100644 --- a/gui/src/app/view/spend/mod.rs +++ b/gui/src/app/view/spend/mod.rs @@ -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() } diff --git a/gui/src/app/view/spend/step.rs b/gui/src/app/view/spend/step.rs index 5f69ad97..c151223a 100644 --- a/gui/src/app/view/spend/step.rs +++ b/gui/src/app/view/spend/step.rs @@ -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() } diff --git a/gui/src/app/view/util.rs b/gui/src/app/view/util.rs index 7f2c9960..94030b9c 100644 --- a/gui/src/app/view/util.rs +++ b/gui/src/app/view/util.rs @@ -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> { amount_with_size(a, TEXT_REGULAR_SIZE) diff --git a/gui/src/app/view/warning.rs b/gui/src/app/view/warning.rs index c4bb5b3f..2e4d61c4 100644 --- a/gui/src/app/view/warning.rs +++ b/gui/src/app/view/warning.rs @@ -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) diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 76f57c16..153a43bc 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -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; diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 82664370..77aa9570 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -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 { diff --git a/gui/src/installer/step/mnemonic.rs b/gui/src/installer/step/mnemonic.rs index 5f88bea9..6cd0f23a 100644 --- a/gui/src/installer/step/mnemonic.rs +++ b/gui/src/installer/step/mnemonic.rs @@ -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, diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index d1119ffa..560ba16e 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -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, diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index a19c9411..2721fe18 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -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) -> Element { ) .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) -> Element { .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) -> Element { 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> { - 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 { value: usize, @@ -1636,7 +1642,7 @@ mod threshsold_input { } } - impl Component for ThresholdInput { + impl Component> for ThresholdInput { type State = (); type Event = Event; @@ -1662,7 +1668,7 @@ mod threshsold_input { fn view(&self, _state: &Self::State) -> Element { 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) }; diff --git a/gui/src/launcher.rs b/gui/src/launcher.rs index 74ab36bf..d92452ab 100644 --- a/gui/src/launcher.rs +++ b/gui/src/launcher.rs @@ -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, 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) diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 782fcf10..65c34e9c 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -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; diff --git a/gui/src/loader.rs b/gui/src/loader.rs index 2c8bb2ec..ed95c998 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -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; diff --git a/gui/src/main.rs b/gui/src/main.rs index 55c84cdc..5a16e8e6 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -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 { diff --git a/gui/src/ui/color.rs b/gui/src/ui/color.rs deleted file mode 100644 index 3e87a13a..00000000 --- a/gui/src/ui/color.rs +++ /dev/null @@ -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, -); diff --git a/gui/src/ui/component/badge.rs b/gui/src/ui/component/badge.rs deleted file mode 100644 index 26ef715b..00000000 --- a/gui/src/ui/component/badge.rs +++ /dev/null @@ -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 + + + + + diff --git a/gui/ui/examples/design-system/src/main.rs b/gui/ui/examples/design-system/src/main.rs new file mode 100644 index 00000000..bbbc5b59 --- /dev/null +++ b/gui/ui/examples/design-system/src/main.rs @@ -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>, + 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) { + 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 { + 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 { + iced_native::subscription::events().map(Self::Message::Event) + } + + fn view(&self) -> Element { + 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; +} diff --git a/gui/ui/examples/design-system/src/section.rs b/gui/ui/examples/design-system/src/section.rs new file mode 100644 index 00000000..930df1ab --- /dev/null +++ b/gui/ui/examples/design-system/src/section.rs @@ -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 { + 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 { + 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 { + 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 { + 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() +} diff --git a/gui/ui/src/color.rs b/gui/ui/src/color.rs new file mode 100644 index 00000000..bfb37d71 --- /dev/null +++ b/gui/ui/src/color.rs @@ -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, + ); +} diff --git a/gui/ui/src/component/badge.rs b/gui/ui/src/component/badge.rs new file mode 100644 index 00000000..1d159026 --- /dev/null +++ b/gui/ui/src/component/badge.rs @@ -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 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() -> 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() -> 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() -> 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)), + ) +} diff --git a/gui/ui/src/component/button.rs b/gui/ui/src/component/button.rs new file mode 100644 index 00000000..75503444 --- /dev/null +++ b/gui/ui/src/component/button.rs @@ -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>, t: &'static str) -> Button<'a, T> { + button::Button::new(content(icon, t)).style(theme::Button::Destructive) +} + +pub fn primary<'a, T: 'a>(icon: Option>, t: &'static str) -> Button<'a, T> { + button::Button::new(content(icon, t)).style(theme::Button::Primary) +} + +pub fn transparent<'a, T: 'a>(icon: Option>, t: &'static str) -> Button<'a, T> { + button::Button::new(content(icon, t)).style(theme::Button::Transparent) +} + +pub fn border<'a, T: 'a>(icon: Option>, 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>, t: &'static str) -> Button<'a, T> { + button(content(icon, t)).style(theme::Button::TransparentBorder) +} + +fn content<'a, T: 'a>(icon: Option>, 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), + } +} diff --git a/gui/ui/src/component/card.rs b/gui/ui/src/component/card.rs new file mode 100644 index 00000000..44c9d1e4 --- /dev/null +++ b/gui/ui/src/component/card.rs @@ -0,0 +1,44 @@ +use crate::{color, component::text::text, icon, theme, widget::*}; + +pub fn simple<'a, T: 'a, C: Into>>(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>>(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)) +} diff --git a/gui/src/ui/component/collapse.rs b/gui/ui/src/component/collapse.rs similarity index 71% rename from gui/src/ui/component/collapse.rs rename to gui/ui/src/component/collapse.rs index 839cbd81..154ebcaa 100644 --- a/gui/src/ui/component/collapse.rs +++ b/gui/ui/src/component/collapse.rs @@ -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 { Collapse(bool), } -impl<'a, Message, T, H, F, C> Component for Collapse<'a, Message, H, F, C> +impl<'a, Message, T, H, F, C> Component> + for Collapse<'a, Message, H, F, C> where T: Into + Clone + 'a, H: Fn() -> Button<'a, Event>, @@ -58,14 +57,13 @@ where fn view(&self, state: &Self::State) -> Element { 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> where Message: 'a, T: Into + Clone + 'a, - H: Fn() -> Button<'a, Event, iced::Renderer>, - F: Fn() -> Button<'a, Event, iced::Renderer>, - C: Fn() -> Element<'a, T, iced::Renderer>, + H: Fn() -> Button<'a, Event>, + F: Fn() -> Button<'a, Event>, + C: Fn() -> Element<'a, T>, { fn from(c: Collapse<'a, Message, H, F, C>) -> Self { iced_lazy::component(c) diff --git a/gui/src/ui/component/form.rs b/gui/ui/src/component/form.rs similarity index 54% rename from gui/src/ui/component/form.rs rename to gui/ui/src/component/form.rs index ddcd5d59..d5682592 100644 --- a/gui/src/ui/component/form.rs +++ b/gui/ui/src/component/form.rs @@ -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 { @@ -24,7 +18,7 @@ impl std::default::Default for Value { } pub struct Form<'a, Message> { - input: TextInput<'a, Message>, + input: text_input::TextInput<'a, Message, iced::Renderer>, 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> 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> 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 for Box> { - fn from(s: InvalidFormStyle) -> Box> { - Box::new(s) - } -} - -impl From for iced::theme::TextInput { - fn from(i: InvalidFormStyle) -> iced::theme::TextInput { - iced::theme::TextInput::Custom(i.into()) - } -} diff --git a/gui/ui/src/component/mod.rs b/gui/ui/src/component/mod.rs new file mode 100644 index 00000000..e8ac9f8e --- /dev/null +++ b/gui/ui/src/component/mod.rs @@ -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)) +} diff --git a/gui/src/ui/component/modal.rs b/gui/ui/src/component/modal.rs similarity index 100% rename from gui/src/ui/component/modal.rs rename to gui/ui/src/component/modal.rs diff --git a/gui/src/ui/component/notification.rs b/gui/ui/src/component/notification.rs similarity index 56% rename from gui/src/ui/component/notification.rs rename to gui/ui/src/component/notification.rs index feedc1f8..212b44e0 100644 --- a/gui/src/ui/component/notification.rs +++ b/gui/ui/src/component/notification.rs @@ -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 for Box> { - fn from(s: WarningStyle) -> Box> { - Box::new(s) - } -} - -impl From for iced::theme::Container { - fn from(i: WarningStyle) -> iced::theme::Container { - iced::theme::Container::Custom(i.into()) - } -} diff --git a/gui/src/ui/component/text.rs b/gui/ui/src/component/text.rs similarity index 76% rename from gui/src/ui/component/text.rs rename to gui/ui/src/component/text.rs index 6cc79a94..a131b188 100644 --- a/gui/src/ui/component/text.rs +++ b/gui/ui/src/component/text.rs @@ -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>) -> iced::widget::Text<'a> { +pub fn text<'a>(content: impl Into>) -> iced::widget::Text<'a, iced::Renderer> { 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> { fn bold(self) -> Self { self.font(font::BOLD) } diff --git a/gui/ui/src/component/tooltip.rs b/gui/ui/src/component/tooltip.rs new file mode 100644 index 00000000..a0fefda7 --- /dev/null +++ b/gui/ui/src/component/tooltip.rs @@ -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)), + ) +} diff --git a/gui/src/ui/font.rs b/gui/ui/src/font.rs similarity index 52% rename from gui/src/ui/font.rs rename to gui/ui/src/font.rs index eb7555d5..c8c4fd98 100644 --- a/gui/src/ui/font.rs +++ b/gui/ui/src/font.rs @@ -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"), }; diff --git a/gui/src/ui/icon.rs b/gui/ui/src/icon.rs similarity index 96% rename from gui/src/ui/icon.rs rename to gui/ui/src/icon.rs index 3b190a90..d71cb4c0 100644 --- a/gui/src/ui/icon.rs +++ b/gui/ui/src/icon.rs @@ -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> { diff --git a/gui/ui/src/lib.rs b/gui/ui/src/lib.rs new file mode 100644 index 00000000..ac38dd55 --- /dev/null +++ b/gui/ui/src/lib.rs @@ -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; + 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; + pub type PickList<'a, Message> = iced::widget::PickList<'a, Message, Renderer>; + pub type Scrollable<'a, Message> = iced::widget::Scrollable<'a, Message, Renderer>; +} diff --git a/gui/ui/src/theme.rs b/gui/ui/src/theme.rs new file mode 100644 index 00000000..927bd0ae --- /dev/null +++ b/gui/ui/src/theme.rs @@ -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 for Overlay { + fn from(_p: PickList) -> Overlay { + Overlay::Default + } +} + +#[derive(Clone, Copy, Default)] +pub enum Text { + #[default] + Default, + Color(iced::Color), +} + +impl From 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 for Container { + fn from(c: Card) -> Container { + Container::Card(c) + } +} + +impl From for Container { + fn from(c: Badge) -> Container { + Container::Badge(c) + } +} + +impl From 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, + } + } +} diff --git a/gui/src/ui/util.rs b/gui/ui/src/util.rs similarity index 92% rename from gui/src/ui/util.rs rename to gui/ui/src/util.rs index 119fdb52..2553c2c9 100644 --- a/gui/src/ui/util.rs +++ b/gui/ui/src/util.rs @@ -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>) -> Self; diff --git a/gui/static/fonts/LICENSE.txt b/gui/ui/static/fonts/LICENSE.txt similarity index 100% rename from gui/static/fonts/LICENSE.txt rename to gui/ui/static/fonts/LICENSE.txt diff --git a/gui/static/fonts/OpenSans-Bold.ttf b/gui/ui/static/fonts/OpenSans-Bold.ttf similarity index 100% rename from gui/static/fonts/OpenSans-Bold.ttf rename to gui/ui/static/fonts/OpenSans-Bold.ttf diff --git a/gui/static/fonts/OpenSans-BoldItalic.ttf b/gui/ui/static/fonts/OpenSans-BoldItalic.ttf similarity index 100% rename from gui/static/fonts/OpenSans-BoldItalic.ttf rename to gui/ui/static/fonts/OpenSans-BoldItalic.ttf diff --git a/gui/static/fonts/OpenSans-ExtraBold.ttf b/gui/ui/static/fonts/OpenSans-ExtraBold.ttf similarity index 100% rename from gui/static/fonts/OpenSans-ExtraBold.ttf rename to gui/ui/static/fonts/OpenSans-ExtraBold.ttf diff --git a/gui/static/fonts/OpenSans-ExtraBoldItalic.ttf b/gui/ui/static/fonts/OpenSans-ExtraBoldItalic.ttf similarity index 100% rename from gui/static/fonts/OpenSans-ExtraBoldItalic.ttf rename to gui/ui/static/fonts/OpenSans-ExtraBoldItalic.ttf diff --git a/gui/static/fonts/OpenSans-Italic.ttf b/gui/ui/static/fonts/OpenSans-Italic.ttf similarity index 100% rename from gui/static/fonts/OpenSans-Italic.ttf rename to gui/ui/static/fonts/OpenSans-Italic.ttf diff --git a/gui/static/fonts/OpenSans-Light.ttf b/gui/ui/static/fonts/OpenSans-Light.ttf similarity index 100% rename from gui/static/fonts/OpenSans-Light.ttf rename to gui/ui/static/fonts/OpenSans-Light.ttf diff --git a/gui/static/fonts/OpenSans-LightItalic.ttf b/gui/ui/static/fonts/OpenSans-LightItalic.ttf similarity index 100% rename from gui/static/fonts/OpenSans-LightItalic.ttf rename to gui/ui/static/fonts/OpenSans-LightItalic.ttf diff --git a/gui/static/fonts/OpenSans-Regular.ttf b/gui/ui/static/fonts/OpenSans-Regular.ttf similarity index 100% rename from gui/static/fonts/OpenSans-Regular.ttf rename to gui/ui/static/fonts/OpenSans-Regular.ttf diff --git a/gui/static/fonts/OpenSans-SemiBold.ttf b/gui/ui/static/fonts/OpenSans-SemiBold.ttf similarity index 100% rename from gui/static/fonts/OpenSans-SemiBold.ttf rename to gui/ui/static/fonts/OpenSans-SemiBold.ttf diff --git a/gui/static/fonts/OpenSans-SemiBoldItalic.ttf b/gui/ui/static/fonts/OpenSans-SemiBoldItalic.ttf similarity index 100% rename from gui/static/fonts/OpenSans-SemiBoldItalic.ttf rename to gui/ui/static/fonts/OpenSans-SemiBoldItalic.ttf diff --git a/gui/static/icons/LICENSE.md b/gui/ui/static/icons/LICENSE.md similarity index 100% rename from gui/static/icons/LICENSE.md rename to gui/ui/static/icons/LICENSE.md diff --git a/gui/static/icons/README.md b/gui/ui/static/icons/README.md similarity index 100% rename from gui/static/icons/README.md rename to gui/ui/static/icons/README.md diff --git a/gui/static/icons/bootstrap-icons.ttf b/gui/ui/static/icons/bootstrap-icons.ttf similarity index 100% rename from gui/static/icons/bootstrap-icons.ttf rename to gui/ui/static/icons/bootstrap-icons.ttf