Merge #597: Iced 0.12

52e32b6a69c751f68e461f4e858170e4ade6619d Update iced patch branch in guix build (edouardparis)
acea67bc35738843144204539833b5767f6c936e Remove transaparency from PickList for tiny-skia (edouardparis)
7801d8cb1f11511cd8e11a556add9e8c62656780 Change descriptor view in installer behind scrollable (edouardparis)
c39d544566a76b126fdc4613197d5b937f72fad3 Use wgpu with tiny-skia fallback (edouardparis)
4920291d04302a5d73196b1d8eeb6ce05d7753a2 fix ui button and badge width (edouardparis)
de371116371c61c5cd2e3bd0d44cdc849f14c5cf Change text size (edouardparis)
a24d9416f62712fc5a5c6fe5d8fa619d06f4006a gui: bump msrv 1.70 (edouardparis)
fdcc30236721f2cf7bdffe788127a35ca376d453 gui: iced-0.12 (edouardparis)
64a626d7e871fd82db27b8ddf773231fe1361755 gui: bump iced 0.10 (edouard)

Pull request description:

  This PR does the migration from iced 0.9 to iced 0.12.

  This new iced version has impact on the fonts size. I reverted the size according to the original UX figma file.

  The new backend renderer is the `wgpu` with `tiny-skia` as a fallback. `wgpu` is the first class citizen of the iced renderers, it supports everything. The `tiny-skia` has some layout problems and does not support some features that is the reason why this PR introduces tiny change in the theme or long string display (ad69711c4a, 88fd0f18e2).

  In order to keep the MSRV as low as possible, a custom patch of the crates `iced_winit`,`iced_style`, `iced_futures` is added to the Cargo.toml

ACKs for top commit:
  darosior:
    ACK 52e32b6a69c751f68e461f4e858170e4ade6619d -- it's been tested a bunch, in particular by Kevin.

Tree-SHA512: 6afda45c227f0dd864c59aee100895af3f0d511c5a425f4d032c5891f900f2e438de778adaa82c5213dba768e608bf7610b6c78d4cb10a2d7ae59f5b132938f4
This commit is contained in:
Antoine Poinsot 2024-05-03 13:32:55 +02:00
commit b602640d16
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
43 changed files with 1979 additions and 1673 deletions

View File

@ -68,7 +68,7 @@ jobs:
strategy:
matrix:
toolchain:
- 1.65.0
- 1.70.0
- nightly
os:
- ubuntu-latest

View File

@ -20,7 +20,7 @@ replace-with = "vendored_sources"
[source."https://github.com/edouardparis/iced"]
git = "https://github.com/edouardparis/iced"
branch = "fix-futures-recipe"
branch = "patch-0.12.3"
replace-with = "vendored_sources"
EOF

2284
gui/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -21,9 +21,8 @@ liana_ui = { path = "ui" }
backtrace = "0.3"
hex = "0.4.3"
iced = { version = "0.9", default-features= false, features = ["tokio", "glow", "svg", "qr_code", "image"] }
iced_native = "0.10"
iced_lazy = { version = "0.6"}
iced = { version = "0.12.1", default-features = false, features = ["tokio", "svg", "qr_code", "image", "lazy", "wgpu"] }
iced_runtime = "0.12.1"
tokio = {version = "1.21.0", features = ["signal"]}
serde = { version = "1.0", features = ["derive"] }
@ -48,8 +47,11 @@ bitcoin_hashes = "0.12"
reqwest = { version = "0.11", default-features=false, features = ["rustls-tls"] }
rust-ini = "0.19.0"
[patch.crates-io]
iced_futures = { git = "https://github.com/edouardparis/iced", branch = "fix-futures-recipe"}
iced_style = { git = "https://github.com/edouardparis/iced", branch = "patch-0.12.3"}
iced_winit = { git = "https://github.com/edouardparis/iced", branch = "patch-0.12.3"}
iced_futures = { git = "https://github.com/edouardparis/iced", branch = "patch-0.12.3"}
[target.'cfg(windows)'.dependencies]
zip = { version = "0.6", default-features=false, features = ["bzip2", "deflate"] }

View File

@ -9,7 +9,7 @@ let
pkgs = import <nixpkgs> {};
in
pkgs.mkShell rec {
buildInputs = [
buildInputs = with pkgs; [
pkgs.expat
pkgs.fontconfig
pkgs.freetype
@ -17,6 +17,8 @@ pkgs.mkShell rec {
pkgs.libGL
pkgs.pkgconfig
pkgs.udev
pkgs.wayland
pkgs.libxkbcommon
pkgs.xorg.libX11
pkgs.xorg.libXcursor
pkgs.xorg.libXi

View File

@ -302,13 +302,13 @@ impl VerifyAddressModal {
}
pub struct ShowQrCodeModal {
qr_code: qr_code::State,
qr_code: qr_code::Data,
address: String,
}
impl ShowQrCodeModal {
pub fn new(address: &Address, index: ChildNumber) -> Option<Self> {
qr_code::State::new(format!("bitcoin:{}?index={}", address, index))
qr_code::Data::new(format!("bitcoin:{}?index={}", address, index))
.ok()
.map(|qr_code| Self {
qr_code,

View File

@ -6,7 +6,6 @@ use liana_ui::{
color,
component::{amount::*, badge, button, form, text::*},
icon, theme,
util::Collection,
widget::*,
};

View File

@ -8,7 +8,6 @@ use liana_ui::{
color,
component::{amount::*, button, card, event, form, text::*},
icon, theme,
util::Collection,
widget::*,
};

View File

@ -29,7 +29,6 @@ use liana_ui::{
},
image::*,
theme,
util::Collection,
widget::*,
};

View File

@ -23,7 +23,6 @@ use liana_ui::{
text::{self, *},
},
icon, theme,
util::Collection,
widget::*,
};
@ -426,7 +425,7 @@ pub fn signatures<'a>(
Container::new(text(alias))
.padding(10)
.style(theme::Container::Pill(theme::Pill::Simple)),
value.to_string(),
liana_ui::widget::Text::new(value.to_string()),
tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Simple)),
@ -439,7 +438,9 @@ pub fn signatures<'a>(
},
)),
)
.horizontal_scroll(scrollable::Properties::new().width(2).scroller_width(2)),
.direction(scrollable::Direction::Horizontal(
scrollable::Properties::new().width(2).scroller_width(2),
)),
)
.padding(15)
} else {
@ -520,7 +521,7 @@ fn container_from_fg(
Container::new(text(alias))
.padding(10)
.style(theme::Container::Pill(theme::Pill::Simple)),
fg.to_string(),
liana_ui::widget::Text::new(fg.to_string()),
tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Simple)),
@ -594,7 +595,9 @@ pub fn path_view<'a>(
)
.push(row_signed),
)
.horizontal_scroll(scrollable::Properties::new().width(2).scroller_width(2))
.direction(scrollable::Direction::Horizontal(
scrollable::Properties::new().width(2).scroller_width(2),
))
.into()
}

View File

@ -4,7 +4,6 @@ use liana_ui::{
color,
component::{amount::*, badge, button, card, form, text::*},
icon, theme,
util::Collection,
widget::*,
};

View File

@ -3,11 +3,10 @@ use std::collections::{HashMap, HashSet};
use iced::{
widget::{
qr_code::{self, QRCode},
scrollable,
scrollable, Space,
},
Alignment, Length,
};
use iced_native::widget::Space;
use liana::miniscript::bitcoin::{
self,
@ -22,7 +21,6 @@ use liana_ui::{
text::{self, *},
},
icon, theme,
util::Collection,
widget::*,
};
@ -94,10 +92,11 @@ pub fn receive<'a>(
Length::Fixed(10.0),
)),
)
.horizontal_scroll(
.direction(scrollable::Direction::Horizontal(
scrollable::Properties::new()
.scroller_width(5),
),
.width(2)
.scroller_width(2),
)),
)
.width(Length::Fill),
)
@ -216,13 +215,13 @@ pub fn verify_address_modal<'a>(
.into()
}
pub fn qr_modal<'a>(qr: &'a qr_code::State, address: &'a String) -> Element<'a, Message> {
pub fn qr_modal<'a>(qr: &'a qr_code::Data, address: &'a String) -> Element<'a, Message> {
Column::new()
.push(
Row::new()
.push(Space::with_width(Length::Fill))
.push(
Container::new(QRCode::new(qr).cell_size(8))
Container::new(QRCode::<liana_ui::theme::Theme>::new(qr).cell_size(8))
.padding(10)
.style(theme::Container::QrCode),
)

View File

@ -13,7 +13,6 @@ use liana::miniscript::bitcoin::{
use liana_ui::{
component::{amount::*, button, form, text::*},
icon, theme,
util::*,
widget::*,
};
@ -144,9 +143,10 @@ pub fn recovery_path_view<'a>(
selected: bool,
) -> Element<'a, Message> {
Row::new()
.push(checkbox("", selected, move |_| {
Message::CreateSpend(CreateSpendMessage::SelectPath(index))
}))
.push(
checkbox("", selected)
.on_toggle(move |_| Message::CreateSpend(CreateSpendMessage::SelectPath(index))),
)
.push(
Column::new()
.push(
@ -170,7 +170,7 @@ pub fn recovery_path_view<'a>(
Container::new(text(alias))
.padding(5)
.style(theme::Container::Pill(theme::Pill::Simple)),
fg.to_string(),
liana_ui::widget::Text::new(fg.to_string()),
tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Simple)),

View File

@ -18,7 +18,6 @@ use liana_ui::{
color,
component::{badge, button, card, form, separation, text::*, tooltip::tooltip},
icon, theme,
util::Collection,
widget::*,
};
@ -633,9 +632,9 @@ pub fn wallet_settings<'a>(
.push(text(descriptor.to_owned()).small())
.push(Space::with_height(Length::Fixed(5.0))),
)
.horizontal_scroll(
.direction(scrollable::Direction::Horizontal(
scrollable::Properties::new().width(5).scroller_width(5),
),
)),
)
.push(
Row::new()

View File

@ -15,7 +15,6 @@ use liana_ui::{
color,
component::{amount::*, badge, button, form, text::*},
icon, theme,
util::Collection,
widget::*,
};
@ -405,9 +404,8 @@ pub fn recipient_view<'a>(
None
})
.push(tooltip::Tooltip::new(
checkbox("MAX", is_max_selected, move |_| {
CreateSpendMessage::SendMaxToRecipient(index)
}),
checkbox("MAX", is_max_selected)
.on_toggle(move |_| CreateSpendMessage::SendMaxToRecipient(index)),
// Add spaces at end so that text is padded at screen edge.
"Total amount remaining after paying fee and any other recipients ",
tooltip::Position::Bottom,
@ -431,9 +429,11 @@ fn coin_list_view<'a>(
Row::new()
.push(
Row::new()
.push(checkbox("", selected, move |_| {
Message::CreateSpend(CreateSpendMessage::SelectCoin(i))
}))
.push(
checkbox("", selected).on_toggle(move |_| {
Message::CreateSpend(CreateSpendMessage::SelectCoin(i))
}),
)
.push(
if let Some(label) = coins_labels.get(&coin.outpoint.to_string()) {
Container::new(p1_regular(label)).width(Length::Fill)

View File

@ -7,7 +7,6 @@ use liana_ui::{
color,
component::{amount::*, badge, button, card, form, text::*},
icon, theme,
util::Collection,
widget::*,
};

View File

@ -1626,7 +1626,7 @@ impl From<BackupDescriptor> for Box<dyn Step> {
#[cfg(test)]
mod tests {
use super::*;
use iced_native::command::Action;
use iced_runtime::command::Action;
use std::sync::{Arc, Mutex};
pub struct Sandbox<S: Step> {

View File

@ -18,7 +18,6 @@ use liana_ui::{
tooltip,
},
icon, image, theme,
util::Collection,
widget::*,
};
@ -319,7 +318,9 @@ pub fn define_descriptor<'a>(
)
.padding(5),
)
.horizontal_scroll(Properties::new().width(3).scroller_width(3)),
.direction(scrollable::Direction::Horizontal(
Properties::new().width(3).scroller_width(3),
)),
),
))
.spacing(10);
@ -436,7 +437,9 @@ pub fn recovery_path_view(
)
.padding(5),
)
.horizontal_scroll(Properties::new().width(3).scroller_width(3)),
.direction(scrollable::Direction::Horizontal(
Properties::new().width(3).scroller_width(3),
)),
),
),
)
@ -568,9 +571,9 @@ pub fn signer_xpubs(xpubs: &[String]) -> Element<Message> {
.push(
Container::new(
scrollable(Container::new(text(xpub).small()).padding(10))
.horizontal_scroll(
.direction(scrollable::Direction::Horizontal(
Properties::new().width(5).scroller_width(5),
),
)),
)
.width(Length::Fill),
)
@ -641,9 +644,9 @@ pub fn hardware_wallet_xpubs<'a>(
.push(
Container::new(
scrollable(Container::new(text(xpub).small()).padding(10))
.horizontal_scroll(
.direction(scrollable::Direction::Horizontal(
Properties::new().width(5).scroller_width(5),
),
)),
)
.width(Length::Fill),
)
@ -716,11 +719,10 @@ pub fn participate_xpub<'a>(
.push(signer)
.width(Length::Fill),
)
.push(checkbox(
"I have shared my extended public key",
shared,
Message::UserActionDone,
))
.push(
checkbox("I have shared my extended public key", shared)
.on_toggle(Message::UserActionDone),
)
.push(if shared && network_valid {
button::primary(None, "Next")
.width(Length::Fixed(200.0))
@ -756,7 +758,16 @@ pub fn register_descriptor<'a>(
.push(card::simple(
Column::new()
.push(text("The descriptor:").small().bold())
.push(text(descriptor.clone()).small())
.push(
scrollable(
Column::new()
.push(text(descriptor.to_owned()).small())
.push(Space::with_height(Length::Fixed(5.0))),
)
.direction(scrollable::Direction::Horizontal(
scrollable::Properties::new().width(5).scroller_width(5),
)),
)
.push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::secondary(Some(icon::clipboard_icon()), "Copy")
@ -803,8 +814,7 @@ pub fn register_descriptor<'a>(
.push_maybe(created_desc.then_some(checkbox(
"I have registered the descriptor on my device(s)",
done,
Message::UserActionDone,
)))
).on_toggle(Message::UserActionDone)))
.push(if !created_desc || (done && !processing) {
button::primary(None, "Next")
.on_press(Message::Next)
@ -858,7 +868,16 @@ pub fn backup_descriptor<'a>(
.push(card::simple(
Column::new()
.push(text("The descriptor:").small().bold())
.push(text(descriptor.clone()).small())
.push(
scrollable(
Column::new()
.push(text(descriptor.to_owned()).small())
.push(Space::with_height(Length::Fixed(5.0))),
)
.direction(scrollable::Direction::Horizontal(
scrollable::Properties::new().width(5).scroller_width(5),
)),
)
.push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::secondary(Some(icon::clipboard_icon()), "Copy")
@ -868,11 +887,9 @@ pub fn backup_descriptor<'a>(
.spacing(10)
.max_width(1000),
))
.push(checkbox(
"I have backed up my descriptor",
done,
Message::UserActionDone,
))
.push(
checkbox("I have backed up my descriptor", done).on_toggle(Message::UserActionDone),
)
.push(if done {
button::primary(None, "Next")
.on_press(Message::Next)
@ -1401,7 +1418,9 @@ pub fn defined_descriptor_key<'a>(
.push(text(name).bold())
.push(Space::with_height(Length::Fixed(5.0))),
)
.horizontal_scroll(Properties::new().width(5).scroller_width(5)),
.direction(scrollable::Direction::Horizontal(
Properties::new().width(5).scroller_width(5),
)),
)
.push(image::success_mark_icon().width(Length::Fixed(50.0)))
.push(Space::with_width(Length::Fixed(1.0))),
@ -1672,7 +1691,7 @@ pub fn edit_sequence_modal<'a>(sequence: &form::Value<String>) -> Element<'a, Me
message::SequenceModal::SequenceEdited(v.to_string()),
))
})
.step(144), // 144 blocks per day
.step(144_u16), // 144 blocks per day
)
.width(Length::Fixed(500.0)),
);
@ -1815,11 +1834,7 @@ pub fn backup_mnemonic<'a>(
)
}),
)
.push(checkbox(
"I have backed up my mnemonic",
done,
Message::UserActionDone,
))
.push(checkbox("I have backed up my mnemonic", done).on_toggle(Message::UserActionDone))
.push(if done {
button::primary(None, "Next")
.on_press(Message::Next)
@ -1997,8 +2012,8 @@ fn layout<'a>(
mod threshsold_input {
use iced::alignment::{self, Alignment};
use iced::widget::{component, Component};
use iced::Length;
use iced_lazy::{self, Component};
use liana_ui::{component::text::*, icon, theme, widget::*};
pub struct ThresholdInput<Message> {
@ -2035,7 +2050,7 @@ mod threshsold_input {
}
}
impl<Message> Component<Message, iced::Renderer<theme::Theme>> for ThresholdInput<Message> {
impl<Message> Component<Message, theme::Theme> for ThresholdInput<Message> {
type State = ();
type Event = Event;
@ -2085,7 +2100,7 @@ mod threshsold_input {
Message: 'a,
{
fn from(numeric_input: ThresholdInput<Message>) -> Self {
iced_lazy::component(numeric_input)
component(numeric_input)
}
}
}

View File

@ -1,6 +1,7 @@
use std::path::PathBuf;
use iced::{
alignment::Horizontal,
widget::{tooltip, Space},
Alignment, Command, Length, Subscription,
};
@ -10,7 +11,6 @@ use liana_ui::{
color,
component::{badge, button, card, modal::Modal, notification, text::*},
icon, image, theme,
util::*,
widget::*,
};
@ -330,7 +330,7 @@ impl DeleteWalletModal {
.push(icon::circle_check_icon().style(color::GREEN))
.push(text("Wallet successfully deleted").style(color::GREEN))
})
.align_x(iced_native::alignment::Horizontal::Center)
.align_x(Horizontal::Center)
.width(Length::Fill),
),
)

View File

@ -19,7 +19,6 @@ use liana_ui::{
color,
component::{button, notification, text::*},
icon,
util::Collection,
widget::*,
};

View File

@ -64,6 +64,7 @@ impl Logger {
&& !metadata.target().starts_with("iced_glow")
&& !metadata.target().starts_with("glow_glyph")
&& !metadata.target().starts_with("naga")
&& !metadata.target().starts_with("winit")
&& !metadata.target().starts_with("mio")
&& !metadata.target().starts_with("ledger_transport_hid")
})),

View File

@ -5,9 +5,9 @@ use std::{error::Error, io::Write, path::PathBuf, process, str::FromStr};
use iced::{
event::{self, Event},
executor,
keyboard::{self, KeyCode},
subscription,
keyboard::{self},
widget::{focus_next, focus_previous},
window::settings::PlatformSpecific,
Application, Command, Settings, Subscription,
};
use tracing::{error, info};
@ -90,12 +90,19 @@ pub enum Key {
#[derive(Debug)]
pub enum Message {
CtrlC,
FontLoaded(Result<(), iced::font::Error>),
Launch(Box<launcher::Message>),
Install(Box<installer::Message>),
Load(Box<loader::Message>),
Run(Box<app::Message>),
Event(iced_native::Event),
KeyPressed(Key),
Event(iced::Event),
}
impl From<Result<(), iced::font::Error>> for Message {
fn from(value: Result<(), iced::font::Error>) -> Self {
Self::FontLoaded(value)
}
}
async fn ctrl_c() -> Result<(), ()> {
@ -121,17 +128,12 @@ impl Application for GUI {
fn new((config, log_level): (Config, Option<LevelFilter>)) -> (GUI, Command<Self::Message>) {
let logger = Logger::setup(log_level.unwrap_or(LevelFilter::INFO));
match config {
let mut cmds = font::loads();
cmds.push(Command::perform(ctrl_c(), |_| Message::CtrlC));
let state = match config {
Config::Launcher(datadir_path) => {
let launcher = Launcher::new(datadir_path);
(
Self {
state: State::Launcher(Box::new(launcher)),
logger,
log_level,
},
Command::perform(ctrl_c(), |_| Message::CtrlC),
)
State::Launcher(Box::new(launcher))
}
Config::Install(datadir_path, network) => {
if !datadir_path.exists() {
@ -151,17 +153,8 @@ impl Application for GUI {
log_level.unwrap_or(LevelFilter::INFO),
);
let (install, command) = Installer::new(datadir_path, network);
(
Self {
state: State::Installer(Box::new(install)),
logger,
log_level,
},
Command::batch(vec![
command.map(|msg| Message::Install(Box::new(msg))),
Command::perform(ctrl_c(), |_| Message::CtrlC),
]),
)
cmds.push(command.map(|msg| Message::Install(Box::new(msg))));
State::Installer(Box::new(install))
}
Config::Run(datadir_path, cfg, network) => {
logger.set_running_mode(
@ -170,37 +163,31 @@ impl Application for GUI {
log_level.unwrap_or_else(|| cfg.log_level().unwrap_or(LevelFilter::INFO)),
);
let (loader, command) = Loader::new(datadir_path, cfg, network, None);
(
Self {
state: State::Loader(Box::new(loader)),
logger,
log_level,
},
Command::batch(vec![
command.map(|msg| Message::Load(Box::new(msg))),
Command::perform(ctrl_c(), |_| Message::CtrlC),
]),
)
cmds.push(command.map(|msg| Message::Load(Box::new(msg))));
State::Loader(Box::new(loader))
}
}
};
(
Self {
state,
logger,
log_level,
},
Command::batch(cmds),
)
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match (&mut self.state, message) {
(_, Message::CtrlC)
| (
_,
Message::Event(iced_native::Event::Window(
iced_native::window::Event::CloseRequested,
)),
) => {
| (_, Message::Event(iced::Event::Window(_, iced::window::Event::CloseRequested))) => {
match &mut self.state {
State::Loader(s) => s.stop(),
State::Launcher(s) => s.stop(),
State::Installer(s) => s.stop(),
State::App(s) => s.stop(),
};
iced::window::close()
iced::window::close(iced::window::Id::MAIN)
}
(_, Message::KeyPressed(Key::Tab(shift))) => {
log::debug!("Tab pressed!");
@ -299,26 +286,26 @@ impl Application for GUI {
State::App(v) => v.subscription().map(|msg| Message::Run(Box::new(msg))),
State::Launcher(v) => v.subscription().map(|msg| Message::Launch(Box::new(msg))),
},
subscription::events_with(|event, status| match (&event, status) {
iced::event::listen_with(|event, status| match (&event, status) {
(
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: KeyCode::Tab,
key: iced::keyboard::Key::Named(iced::keyboard::key::Named::Tab),
modifiers,
..
}),
event::Status::Ignored,
) => Some(Message::KeyPressed(Key::Tab(modifiers.shift()))),
(
iced::Event::Window(iced_native::window::Event::CloseRequested),
iced::Event::Window(_, iced::window::Event::CloseRequested),
event::Status::Ignored,
) => Some(Message::Event(event)),
_ => None,
}),
])
.with_filter(|(event, _status)| {
.with_filter(|event| {
matches!(
event,
iced::Event::Window(iced_native::window::Event::CloseRequested)
iced::Event::Window(_, iced::window::Event::CloseRequested)
| iced::Event::Keyboard(_)
)
})
@ -453,11 +440,19 @@ fn main() -> Result<(), Box<dyn Error>> {
setup_panic_hook();
let mut settings = Settings::with_flags((config, log_level));
settings.id = Some("liana-gui".to_string());
settings.window.icon = Some(image::liana_app_icon());
settings.default_text_size = text::P1_SIZE.into();
settings.default_font = Some(font::REGULAR_BYTES);
settings.exit_on_close_request = false;
settings.default_font = liana_ui::font::REGULAR;
settings.window.exit_on_close_request = false;
settings.id = Some("Liana".to_string());
#[cfg(target_os = "linux")]
{
settings.window.platform_specific = PlatformSpecific {
application_id: "Liana".to_string(),
};
}
if let Err(e) = GUI::run(settings) {
return Err(format!("Failed to launch UI: {}", e).into());

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use iced_native::command::Action;
use iced_runtime::command::Action;
use crate::{
app::{cache::Cache, message::Message, state::State, wallet::Wallet},

View File

@ -6,8 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = { version = "0.9", default_features = false, features = ["svg", "image", "glow"] }
iced_native = "0.10"
iced_lazy = { version = "0.6"}
iced = { version = "0.12.1", default-features = false, features = ["svg", "image", "lazy", "qr_code", "advanced", "webgl"] }
bitcoin = "0.31"
chrono = "0.4"

View File

@ -6,8 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = "0.9"
iced_native = "0.10"
iced = "0.10"
web-sys = "0.3.61"
chrono = "0.4"
liana_ui = { path = "../.." }

View File

@ -2,11 +2,12 @@ mod section;
use iced::widget::{button, column, container, row, scrollable, text, Space};
use iced::{executor, Application, Command, Length, Settings, Subscription};
use liana_ui::{component::text::*, image, theme, widget::*};
use liana_ui::{component::text::*, font, image, theme, widget::*};
pub fn main() -> iced::Result {
let mut settings = Settings::with_flags(Config {});
settings.default_text_size = P1_SIZE.into();
settings.default_font = font::REGULAR;
DesignSystem::run(settings)
}
@ -21,11 +22,18 @@ struct DesignSystem {
#[derive(Debug, Clone)]
pub enum Message {
Event(iced_native::Event),
FontLoaded(Result<(), iced::font::Error>),
Event(iced::Event),
Section(usize),
Ignore,
}
impl From<Result<(), iced::font::Error>> for Message {
fn from(res: Result<(), iced::font::Error>) -> Message {
Message::FontLoaded(res)
}
}
impl Application for DesignSystem {
type Message = Message;
type Theme = theme::Theme;
@ -49,6 +57,9 @@ impl Application for DesignSystem {
],
current: 0,
};
#[allow(unused_mut)]
let mut cmds: Vec<Command<Self::Message>> = font::loads();
#[cfg(target_arch = "wasm32")]
{
use iced_native::{command, window};
@ -57,16 +68,12 @@ impl Application for DesignSystem {
(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,
})),
)
cmds.push(Command::single(command::Action::Window(
window::Action::Resize { width, height },
)));
}
#[cfg(not(target_arch = "wasm32"))]
(app, Command::none())
(app, Command::batch(cmds))
}
fn update(&mut self, message: Message) -> Command<Self::Message> {
@ -76,10 +83,7 @@ impl Application for DesignSystem {
self.current = i;
}
}
Message::Event(iced::Event::Window(iced_native::window::Event::Resized {
width,
height,
})) => {
Message::Event(iced::Event::Window(iced::window::Event::Resized { width, height })) => {
#[cfg(target_arch = "wasm32")]
{
use iced_native::{command, window};
@ -95,7 +99,7 @@ impl Application for DesignSystem {
}
fn subscription(&self) -> Subscription<Self::Message> {
iced_native::subscription::events().map(Self::Message::Event)
iced::subscription::events().map(Self::Message::Event)
}
fn view(&self) -> Element<Message> {

View File

@ -1,6 +1,6 @@
pub use bitcoin::Amount;
use crate::{color, component::text::*, util::Collection, widget::*};
use crate::{color, component::text::*, widget::*};
pub fn amount<'a, T: 'a>(a: &Amount) -> Row<'a, T> {
amount_with_size(a, P1_SIZE)

View File

@ -108,7 +108,6 @@ pub fn badge_pill<'a, T: 'a>(label: &'a str, tooltip: &'a str) -> Container<'a,
tooltip::Tooltip::new(
Container::new(text::p2_regular(label))
.padding(10)
.width(Length::Fill)
.center_x()
.style(theme::Container::Pill(theme::Pill::Simple)),
tooltip,

View File

@ -1,19 +1,28 @@
use crate::{color, theme, widget::*};
use iced::widget::{button, container, row};
use iced::{Alignment, Length};
use iced::Alignment;
use super::text::text;
pub fn menu<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon.map(|i| i.style(color::GREY_3)), t).padding(10))
button::Button::new(content_menu(icon.map(|i| i.style(color::GREY_3)), t).padding(10))
.style(theme::Button::Menu(false))
}
pub fn menu_active<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon.map(|i| i.style(color::GREY_3)), t).padding(10))
button::Button::new(content_menu(icon.map(|i| i.style(color::GREY_3)), t).padding(10))
.style(theme::Button::Menu(true))
}
fn content_menu<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Container<'a, T> {
match icon {
None => container(text(t)).padding(5),
Some(i) => {
container(row![i, text(t)].spacing(10).align_items(Alignment::Center)).padding(5)
}
}
}
pub fn alert<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Destructive)
}
@ -40,15 +49,9 @@ pub fn transparent_border<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) ->
fn content<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Container<'a, T> {
match icon {
None => container(text(t)).width(Length::Fill).center_x().padding(5),
Some(i) => container(
row![i, text(t)]
.spacing(10)
.width(iced::Length::Fill)
.align_items(Alignment::Center),
)
.width(iced::Length::Fill)
.center_x()
.padding(5),
None => container(text(t)).center_x().padding(5),
Some(i) => container(row![i, text(t)].spacing(10).align_items(Alignment::Center))
.center_x()
.padding(5),
}
}

View File

@ -34,7 +34,7 @@ pub fn error<'a, T: 'a>(message: &'static str, error: String) -> Container<'a, T
.align_items(iced::Alignment::Center)
.push(icon::warning_icon().style(color::RED))
.push(text(message).style(color::RED)),
error,
Text::new(error),
iced::widget::tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Error)),

View File

@ -1,6 +1,8 @@
use crate::widget::*;
use iced::widget::column;
use iced_lazy::{self, Component};
use iced::{
advanced,
widget::{column, component, Button, Component},
Element,
};
use std::marker::PhantomData;
pub struct Collapse<'a, M, H, F, C> {
@ -10,13 +12,15 @@ pub struct Collapse<'a, M, H, F, C> {
phantom: PhantomData<&'a M>,
}
impl<'a, Message, T, H, F, C> Collapse<'a, Message, H, F, C>
impl<'a, Message, T, H, F, C, Theme, Renderer> Collapse<'a, Message, H, F, C>
where
Renderer: advanced::Renderer,
Theme: iced::widget::button::StyleSheet,
Message: 'a,
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>> + 'a,
F: Fn() -> Button<'a, Event<T>> + 'a,
C: Fn() -> Element<'a, T> + 'a,
H: Fn() -> Button<'a, Event<T>, Theme, Renderer> + 'a,
F: Fn() -> Button<'a, Event<T>, Theme, Renderer> + 'a,
C: Fn() -> Element<'a, T, Theme, Renderer> + 'a,
{
pub fn new(before: H, after: F, content: C) -> Self {
Collapse {
@ -34,13 +38,15 @@ pub enum Event<T> {
Collapse(bool),
}
impl<'a, Message, T, H, F, C> Component<Message, iced::Renderer<crate::theme::Theme>>
impl<'a, Message, T, H, F, C, Theme, Renderer> Component<Message, Theme, Renderer>
for Collapse<'a, Message, H, F, C>
where
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>>,
F: Fn() -> Button<'a, Event<T>>,
C: Fn() -> Element<'a, T>,
H: Fn() -> Button<'a, Event<T>, Theme, Renderer>,
F: Fn() -> Button<'a, Event<T>, Theme, Renderer>,
C: Fn() -> Element<'a, T, Theme, Renderer>,
Renderer: 'a + advanced::Renderer,
Theme: 'a + iced::widget::button::StyleSheet,
{
type State = bool;
type Event = Event<T>;
@ -55,7 +61,7 @@ where
}
}
fn view(&self, state: &Self::State) -> Element<Self::Event> {
fn view(&self, state: &Self::State) -> Element<Self::Event, Theme, Renderer> {
if *state {
column![
(self.after)().on_press(Event::Collapse(false)),
@ -68,16 +74,18 @@ where
}
}
impl<'a, Message, T, H: 'a, F: 'a, C: 'a> From<Collapse<'a, Message, H, F, C>>
for Element<'a, Message>
impl<'a, Message, T, H: 'a, F: 'a, C: 'a, Theme, Renderer> From<Collapse<'a, Message, H, F, C>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer: 'a + advanced::Renderer,
Theme: 'a + iced::widget::button::StyleSheet,
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>>,
F: Fn() -> Button<'a, Event<T>>,
C: Fn() -> Element<'a, T>,
H: Fn() -> Button<'a, Event<T>, Theme, Renderer>,
F: Fn() -> Button<'a, Event<T>, Theme, Renderer>,
C: Fn() -> Element<'a, T, Theme, Renderer>,
{
fn from(c: Collapse<'a, Message, H, F, C>) -> Self {
iced_lazy::component(c)
component(c)
}
}

View File

@ -2,7 +2,6 @@ use crate::{
color,
component::{amount, badge, text},
theme,
util::Collection,
widget::*,
};
use bitcoin::Amount;
@ -12,7 +11,7 @@ use iced::{
};
pub fn unconfirmed_outgoing_event<'a, T: Clone + 'a>(
label: Option<iced::widget::Text<'a, iced::Renderer<theme::Theme>>>,
label: Option<Text<'a>>,
amount: &Amount,
msg: T,
) -> Container<'a, T> {
@ -39,7 +38,7 @@ pub fn unconfirmed_outgoing_event<'a, T: Clone + 'a>(
}
pub fn confirmed_outgoing_event<'a, T: Clone + 'a>(
label: Option<iced::widget::Text<'a, iced::Renderer<theme::Theme>>>,
label: Option<Text<'a>>,
date: chrono::NaiveDateTime,
amount: &Amount,
msg: T,
@ -72,7 +71,7 @@ pub fn confirmed_outgoing_event<'a, T: Clone + 'a>(
}
pub fn unconfirmed_incoming_event<'a, T: Clone + 'a>(
label: Option<iced::widget::Text<'a, iced::Renderer<theme::Theme>>>,
label: Option<Text<'a>>,
amount: &Amount,
msg: T,
) -> Container<'a, T> {
@ -99,7 +98,7 @@ pub fn unconfirmed_incoming_event<'a, T: Clone + 'a>(
}
pub fn confirmed_incoming_event<'a, T: Clone + 'a>(
label: Option<iced::widget::Text<'a, iced::Renderer<theme::Theme>>>,
label: Option<Text<'a>>,
date: chrono::NaiveDateTime,
amount: &Amount,
msg: T,

View File

@ -1,7 +1,7 @@
use bitcoin::Denomination;
use iced::{widget::text_input, Length};
use crate::{color, component::text, theme, util::Collection, widget::*};
use crate::{color, component::text, theme, widget::*};
#[derive(Debug, Clone)]
pub struct Value<T> {
@ -19,7 +19,7 @@ impl std::default::Default for Value<String> {
}
pub struct Form<'a, Message> {
input: text_input::TextInput<'a, Message, iced::Renderer<theme::Theme>>,
input: TextInput<'a, Message>,
warning: Option<&'a str>,
valid: bool,
}

View File

@ -1,4 +1,4 @@
use crate::{color, component::text, icon, image, theme, util::*, widget::*};
use crate::{color, component::text, icon, image, theme, widget::*};
use iced::{
widget::{column, container, row, tooltip},
Alignment, Length,

View File

@ -1,23 +1,26 @@
/// modal widget from https://github.com/iced-rs/iced/blob/master/examples/modal/
use iced_native::alignment::Alignment;
use iced_native::widget::{self, Tree};
use iced_native::{
event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length,
Point, Rectangle, Shell, Size, Widget,
};
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{self, Tree, Widget};
use iced::advanced::{self, Clipboard, Shell};
use iced::alignment::Alignment;
use iced::event;
use iced::mouse;
use iced::{Color, Element, Event, Length, Point, Rectangle, Size, Vector};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
pub struct Modal<'a, Message, Theme, Renderer> {
base: Element<'a, Message, Theme, Renderer>,
modal: Element<'a, Message, Theme, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
impl<'a, Message, Theme, Renderer> Modal<'a, Message, Theme, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
base: impl Into<Element<'a, Message, Theme, Renderer>>,
modal: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self {
Self {
base: base.into(),
@ -33,9 +36,10 @@ impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer>
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Modal<'a, Message, Theme, Renderer>
where
Renderer: iced_native::Renderer,
Renderer: advanced::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
@ -46,47 +50,52 @@ where
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
fn size(&self) -> Size<Length> {
self.base.as_widget().size()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
fn layout(
&self,
tree: &mut widget::Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.base
.as_widget()
.layout(&mut tree.children[0], renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
state: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
cursor,
renderer,
clipboard,
shell,
viewport,
)
}
fn draw(
&self,
state: &Tree,
state: &widget::Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
@ -95,7 +104,7 @@ where
theme,
style,
layout,
cursor_position,
cursor,
viewport,
);
}
@ -105,30 +114,29 @@ where
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
Some(overlay::Element::new(Box::new(Overlay {
position: layout.position() + translation,
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
})))
}
fn mouse_interaction(
&self,
state: &Tree,
state: &widget::Tree,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
cursor,
viewport,
renderer,
)
@ -147,38 +155,39 @@ where
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
struct Overlay<'a, 'b, Message, Theme, Renderer> {
position: Point,
content: &'b mut Element<'a, Message, Theme, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
impl<'a, 'b, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer>
where
Renderer: iced_native::Renderer,
Renderer: advanced::Renderer,
Message: Clone,
{
fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node {
fn layout(&mut self, renderer: &Renderer, _bounds: Size) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let child = self
.content
.as_widget()
.layout(self.tree, renderer, &limits)
.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
layout::Node::with_children(self.size, vec![child]).move_to(self.position)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@ -187,7 +196,7 @@ where
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event {
if !content_bounds.contains(cursor_position) {
if !cursor.is_over(content_bounds) {
shell.publish(message.clone());
return event::Status::Captured;
}
@ -198,27 +207,26 @@ where
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
cursor,
renderer,
clipboard,
shell,
&layout.bounds(),
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
..renderer::Quad::default()
},
Color {
a: 0.80,
@ -232,7 +240,7 @@ where
theme,
style,
layout.children().next().unwrap(),
cursor_position,
cursor,
&layout.bounds(),
);
}
@ -254,26 +262,41 @@ where
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
cursor,
viewport,
renderer,
)
}
fn overlay<'c>(
&'c mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
self.tree,
layout.children().next().unwrap(),
renderer,
Vector::ZERO,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer>
impl<'a, Message, Theme, Renderer> From<Modal<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Renderer: 'a + advanced::Renderer,
Message: 'a + Clone,
Theme: 'a,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
fn from(modal: Modal<'a, Message, Theme, Renderer>) -> Self {
Element::new(modal)
}
}

View File

@ -5,7 +5,6 @@ use crate::{
color,
component::{collapse, text},
icon, theme,
util::*,
widget::*,
};
use iced::{

View File

@ -1,122 +1,94 @@
use crate::{font, theme::Theme};
use std::borrow::Cow;
// 40 * 1.2
pub const H1_SIZE: u16 = 48;
// 29 * 1.2
pub const H2_SIZE: u16 = 35;
// 24 * 1.2
pub const H3_SIZE: u16 = 29;
// 20 * 1.2
pub const H4_SIZE: u16 = 24;
// 18 * 1.2
pub const H5_SIZE: u16 = 22;
// 16 * 1.2
pub const P1_SIZE: u16 = 20;
// 14 * 1.2
pub const P2_SIZE: u16 = 17;
// 12 * 1.2
pub const CAPTION_SIZE: u16 = 15;
pub const H1_SIZE: u16 = 40;
pub const H2_SIZE: u16 = 29;
pub const H3_SIZE: u16 = 24;
pub const H4_SIZE: u16 = 20;
pub const H5_SIZE: u16 = 18;
pub const P1_SIZE: u16 = 16;
pub const P2_SIZE: u16 = 14;
pub const CAPTION_SIZE: u16 = 12;
pub fn h1<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h1<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::BOLD)
.size(H1_SIZE)
}
pub fn h2<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h2<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::BOLD)
.size(H2_SIZE)
}
pub fn h3<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h3<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::BOLD)
.size(H3_SIZE)
}
pub fn h4_bold<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h4_bold<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::BOLD)
.size(H4_SIZE)
}
pub fn h4_regular<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h4_regular<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(H4_SIZE)
}
pub fn h5_medium<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h5_medium<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::MEDIUM)
.size(H5_SIZE)
}
pub fn h5_regular<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn h5_regular<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(H5_SIZE)
}
pub fn p1_bold<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn p1_bold<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::BOLD)
.size(P1_SIZE)
}
pub fn p1_medium<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn p1_medium<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::MEDIUM)
.size(P1_SIZE)
}
pub fn p1_regular<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn p1_regular<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(P1_SIZE)
}
pub fn p2_medium<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn p2_medium<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::MEDIUM)
.size(P2_SIZE)
}
pub fn p2_regular<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn p2_regular<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(P2_SIZE)
}
pub fn caption<'a>(
content: impl Into<Cow<'a, str>>,
) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn caption<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
iced::widget::Text::new(content)
.font(font::REGULAR)
.size(CAPTION_SIZE)
}
pub fn text<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, iced::Renderer<Theme>> {
pub fn text<'a>(content: impl Into<Cow<'a, str>>) -> iced::widget::Text<'a, Theme> {
p1_regular(content)
}
@ -125,7 +97,7 @@ pub trait Text {
fn small(self) -> Self;
}
impl Text for iced::widget::Text<'_, iced::Renderer<Theme>> {
impl Text for iced::widget::Text<'_, Theme> {
fn bold(self) -> Self {
self.font(font::BOLD)
}

View File

@ -1,29 +1,28 @@
use std::time::Instant;
use super::theme::Theme;
use iced::advanced::widget::{Operation, Tree};
use iced::advanced::{layout, mouse, overlay, renderer};
use iced::advanced::{Clipboard, Layout, Shell, Widget};
use iced::event::{self, Event};
use iced::{Alignment, Element, Length, Point, Rectangle, Size, Vector};
use iced_native::widget::{Operation, Tree};
use iced_native::{event, layout, mouse, overlay, renderer};
use iced_native::{Clipboard, Event, Layout, Shell, Widget};
pub trait Toast {
fn title(&self) -> &str;
fn body(&self) -> &str;
}
pub struct Manager<'a, Message, Renderer> {
content: Element<'a, Message, Renderer>,
toasts: Vec<Element<'a, Message, Renderer>>,
pub struct Manager<'a, Message, Theme, Renderer> {
content: Element<'a, Message, Theme, Renderer>,
toasts: Vec<Element<'a, Message, Theme, Renderer>>,
}
impl<'a, Message> Manager<'a, Message, iced::Renderer<Theme>>
impl<'a, Message, Theme> Manager<'a, Message, Theme, iced::Renderer>
where
Message: 'a + Clone,
{
pub fn new(
content: impl Into<Element<'a, Message, iced::Renderer<Theme>>>,
toasts: Vec<Element<'a, Message, iced::Renderer<Theme>>>,
content: impl Into<Element<'a, Message, Theme, iced::Renderer>>,
toasts: Vec<Element<'a, Message, Theme, iced::Renderer>>,
) -> Self {
Self {
content: content.into(),
@ -32,29 +31,33 @@ where
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Manager<'a, Message, Renderer>
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Manager<'a, Message, Theme, Renderer>
where
Renderer: iced_native::Renderer,
Renderer: iced::advanced::Renderer,
{
fn width(&self) -> Length {
self.content.as_widget().width()
fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}
fn height(&self) -> Length {
self.content.as_widget().height()
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.content
.as_widget()
.layout(&mut tree.children[0], renderer, limits)
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.content.as_widget().layout(renderer, limits)
}
fn tag(&self) -> iced_native::widget::tree::Tag {
fn tag(&self) -> iced::advanced::widget::tree::Tag {
struct Marker(Vec<Instant>);
iced_native::widget::tree::Tag::of::<Marker>()
iced::advanced::widget::tree::Tag::of::<Marker>()
}
fn state(&self) -> iced_native::widget::tree::State {
iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
fn state(&self) -> iced::advanced::widget::tree::State {
iced::advanced::widget::tree::State::new(Vec::<Option<Instant>>::new())
}
fn children(&self) -> Vec<Tree> {
@ -95,7 +98,7 @@ where
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
operation.container(None, &mut |operation| {
operation.container(None, layout.bounds(), &mut |operation| {
self.content
.as_widget()
.operate(&mut state.children[0], layout, renderer, operation);
@ -107,10 +110,11 @@ where
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor_position: iced::mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
self.content.as_widget_mut().on_event(
&mut state.children[0],
@ -120,6 +124,7 @@ where
renderer,
clipboard,
shell,
viewport,
)
}
@ -127,10 +132,10 @@ where
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor_position: iced::mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@ -148,7 +153,7 @@ where
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
cursor_position: iced::mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@ -166,25 +171,26 @@ where
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let instants = state.state.downcast_mut::<Vec<Option<Instant>>>();
let (content_state, toasts_state) = state.children.split_at_mut(1);
let content = self
.content
.as_widget_mut()
.overlay(&mut content_state[0], layout, renderer);
let content = self.content.as_widget_mut().overlay(
&mut content_state[0],
layout,
renderer,
translation,
);
let toasts = (!self.toasts.is_empty()).then(|| {
overlay::Element::new(
layout.bounds().position(),
Box::new(Overlay {
toasts: &mut self.toasts,
state: toasts_state,
instants,
}),
)
overlay::Element::new(Box::new(Overlay {
position: layout.bounds().position() + translation,
toasts: &mut self.toasts,
state: toasts_state,
instants,
}))
});
let overlays = content.into_iter().chain(toasts).collect::<Vec<_>>();
@ -192,18 +198,19 @@ where
}
}
struct Overlay<'a, 'b, Message, Renderer> {
toasts: &'b mut [Element<'a, Message, Renderer>],
struct Overlay<'a, 'b, Message, Theme, Renderer> {
position: Point,
toasts: &'b mut [Element<'a, Message, Theme, Renderer>],
state: &'b mut [Tree],
instants: &'b mut [Option<Instant>],
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
impl<'a, 'b, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer>
where
Renderer: iced_native::Renderer,
Renderer: iced::advanced::Renderer,
{
fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> layout::Node {
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, bounds)
.width(Length::Fill)
.height(Length::Fill);
@ -212,23 +219,27 @@ where
layout::flex::Axis::Vertical,
renderer,
&limits,
Length::Fill,
Length::Fill,
10.into(),
10.0,
Alignment::End,
self.toasts,
self.state,
)
.translate(Vector::new(position.x, position.y))
.translate(Vector::new(self.position.x, self.position.y))
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor_position: iced::mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let viewport = layout.bounds();
self.toasts
.iter_mut()
.zip(self.state.iter_mut())
@ -246,6 +257,7 @@ where
renderer,
clipboard,
&mut local_shell,
&viewport,
);
if !local_shell.is_empty() {
@ -262,10 +274,10 @@ where
fn draw(
&self,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor_position: iced::mouse::Cursor,
) {
let viewport = layout.bounds();
@ -291,9 +303,9 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_native::widget::Operation<Message>,
operation: &mut dyn iced::advanced::widget::Operation<Message>,
) {
operation.container(None, &mut |operation| {
operation.container(None, layout.bounds(), &mut |operation| {
self.toasts
.iter()
.zip(self.state.iter_mut())
@ -309,7 +321,7 @@ where
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor_position: iced::mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@ -330,19 +342,21 @@ where
.unwrap_or_default()
}
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
fn is_over(&self, layout: Layout<'_>, _renderer: &Renderer, cursor_position: Point) -> bool {
layout
.children()
.any(|layout| layout.bounds().contains(cursor_position))
}
}
impl<'a, Message, Renderer> From<Manager<'a, Message, Renderer>> for Element<'a, Message, Renderer>
impl<'a, Message, Theme, Renderer> From<Manager<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Renderer: 'a + iced::advanced::Renderer,
Message: 'a,
Theme: 'a,
{
fn from(manager: Manager<'a, Message, Renderer>) -> Self {
fn from(manager: Manager<'a, Message, Theme, Renderer>) -> Self {
Element::new(manager)
}
}

View File

@ -1,18 +1,37 @@
use iced::Font;
pub const BOLD: Font = Font::External {
name: "Bold",
bytes: include_bytes!("../static/fonts/IBMPlexSans-Bold.ttf"),
use iced::{
font::{Family, Stretch, Weight},
Command, Font,
};
pub const MEDIUM: Font = Font::External {
name: "Regular",
bytes: include_bytes!("../static/fonts/IBMPlexSans-Medium.ttf"),
pub const BOLD: Font = Font {
family: Family::Name("IBM Plex Sans"),
weight: Weight::Bold,
style: iced::font::Style::Normal,
stretch: Stretch::Normal,
};
pub const MEDIUM: Font = Font {
family: Family::Name("IBM Plex Sans"),
weight: Weight::Medium,
style: iced::font::Style::Normal,
stretch: Stretch::Normal,
};
pub const REGULAR: Font = Font::with_name("IBM Plex Sans");
pub const BOLD_BYTES: &[u8] = include_bytes!("../static/fonts/IBMPlexSans-Bold.ttf");
pub const MEDIUM_BYTES: &[u8] = include_bytes!("../static/fonts/IBMPlexSans-Medium.ttf");
pub const REGULAR_BYTES: &[u8] = include_bytes!("../static/fonts/IBMPlexSans-Regular.ttf");
pub const REGULAR: Font = Font::External {
name: "Regular",
bytes: REGULAR_BYTES,
};
pub const ICONEX_ICONS_BYTES: &[u8] = include_bytes!("../static/icons/iconex/iconex-icons.ttf");
pub const BOOTSTRAP_ICONS_BYTE: &[u8] = include_bytes!("../static/icons/bootstrap-icons.ttf");
pub fn loads<T: From<Result<(), iced::font::Error>> + 'static>() -> Vec<Command<T>> {
vec![
iced::font::load(BOLD_BYTES).map(T::from),
iced::font::load(MEDIUM_BYTES).map(T::from),
iced::font::load(REGULAR_BYTES).map(T::from),
iced::font::load(ICONEX_ICONS_BYTES).map(T::from),
iced::font::load(BOOTSTRAP_ICONS_BYTE).map(T::from),
]
}

View File

@ -1,10 +1,7 @@
use crate::{component::text::P1_SIZE, widget::*};
use iced::{alignment, Font, Length};
const BOOTSTRAP_ICONS: Font = Font::External {
name: "Bootstrap icons",
bytes: include_bytes!("../static/icons/bootstrap-icons.ttf"),
};
const BOOTSTRAP_ICONS: Font = Font::with_name("bootstrap-icons");
fn bootstrap_icon(unicode: char) -> Text<'static> {
Text::new(unicode.to_string())
@ -118,10 +115,7 @@ pub fn previous_icon() -> Text<'static> {
bootstrap_icon('\u{F284}')
}
const ICONEX_ICONS: Font = Font::External {
name: "Iconex icons",
bytes: include_bytes!("../static/icons/iconex/iconex-icons.ttf"),
};
const ICONEX_ICONS: Font = Font::with_name("Untitled1");
fn iconex_icon(unicode: char) -> Text<'static> {
Text::new(unicode.to_string())

View File

@ -4,22 +4,24 @@ pub mod font;
pub mod icon;
pub mod image;
pub mod theme;
pub mod util;
pub mod widget {
#![allow(dead_code)]
use crate::theme::Theme;
pub type Renderer = iced::Renderer<Theme>;
pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>;
pub type Container<'a, Message> = iced::widget::Container<'a, Message, Renderer>;
pub type Column<'a, Message> = iced::widget::Column<'a, Message, Renderer>;
pub type Row<'a, Message> = iced::widget::Row<'a, Message, Renderer>;
pub type Button<'a, Message> = iced::widget::Button<'a, Message, Renderer>;
pub type Text<'a> = iced::widget::Text<'a, Renderer>;
pub type Tooltip<'a> = iced::widget::Tooltip<'a, Renderer>;
pub type ProgressBar = iced::widget::ProgressBar<Renderer>;
pub type PickList<'a, Message> = iced::widget::PickList<'a, Message, Renderer>;
pub type Scrollable<'a, Message> = iced::widget::Scrollable<'a, Message, Renderer>;
pub type Svg = iced::widget::Svg<Renderer>;
pub type Renderer = iced::Renderer;
pub type Element<'a, Message> = iced::Element<'a, Message, Theme, Renderer>;
pub type Container<'a, Message> = iced::widget::Container<'a, Message, Theme, Renderer>;
pub type Column<'a, Message> = iced::widget::Column<'a, Message, Theme, Renderer>;
pub type Row<'a, Message> = iced::widget::Row<'a, Message, Theme, Renderer>;
pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme, Renderer>;
pub type CheckBox<'a, Message> = iced::widget::Checkbox<'a, Message, Theme, Renderer>;
pub type Text<'a> = iced::widget::Text<'a, Theme, Renderer>;
pub type TextInput<'a, Message> = iced::widget::TextInput<'a, Message, Theme, Renderer>;
pub type Tooltip<'a> = iced::widget::Tooltip<'a, Theme, Renderer>;
pub type ProgressBar = iced::widget::ProgressBar<Theme>;
pub type PickList<'a, T, L, V, Message> =
iced::widget::PickList<'a, T, L, V, Message, Theme, Renderer>;
pub type Scrollable<'a, Message> = iced::widget::Scrollable<'a, Message, Theme, Renderer>;
pub type Svg = iced::widget::Svg<Theme>;
}

View File

@ -1,8 +1,8 @@
use iced::{
application,
widget::{
button, checkbox, container, pick_list, progress_bar, radio, scrollable, slider, svg, text,
text_input,
button, checkbox, container, pick_list, progress_bar, qr_code, radio, scrollable, slider,
svg, text, text_input,
},
};
@ -44,9 +44,11 @@ impl iced::overlay::menu::StyleSheet for Theme {
iced::overlay::menu::Appearance {
text_color: color::GREY_2,
background: color::GREY_6.into(),
border_width: 0.0,
border_radius: 25.0,
border_color: color::GREY_2,
border: iced::Border {
color: color::GREY_2,
width: 0.0,
radius: 25.0.into(),
},
selected_text_color: color::LIGHT_BLACK,
selected_background: color::GREEN.into(),
}
@ -103,21 +105,24 @@ impl container::StyleSheet for Theme {
match self {
Theme::Light => match style {
Container::Transparent => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
background: Some(iced::Color::TRANSPARENT.into()),
..container::Appearance::default()
},
Container::Background => container::Appearance {
background: color::GREY_2.into(),
background: Some(color::GREY_2.into()),
..container::Appearance::default()
},
Container::Foreground => container::Appearance {
background: color::GREY_2.into(),
background: Some(color::GREY_2.into()),
..container::Appearance::default()
},
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::LIGHT_BLACK,
background: Some(iced::Color::TRANSPARENT.into()),
border: iced::Border {
color: color::LIGHT_BLACK,
width: 1.0,
radius: 0.0.into(),
},
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
@ -125,32 +130,39 @@ impl container::StyleSheet for Theme {
Container::Pill(c) => c.appearance(self),
Container::Notification(c) => c.appearance(self),
Container::Custom(c) => container::Appearance {
background: (*c).into(),
background: Some((*c).into()),
..container::Appearance::default()
},
Container::QrCode => container::Appearance {
background: color::WHITE.into(),
border_radius: 25.0,
background: Some(color::WHITE.into()),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
},
Theme::Dark => match style {
Container::Transparent => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
background: Some(iced::Color::TRANSPARENT.into()),
..container::Appearance::default()
},
Container::Background => container::Appearance {
background: color::LIGHT_BLACK.into(),
background: Some(color::LIGHT_BLACK.into()),
..container::Appearance::default()
},
Container::Foreground => container::Appearance {
background: color::BLACK.into(),
background: Some(color::BLACK.into()),
..container::Appearance::default()
},
Container::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::GREY_3,
background: Some(iced::Color::TRANSPARENT.into()),
border: iced::Border {
color: color::GREY_3,
width: 1.0,
radius: 0.0.into(),
},
..container::Appearance::default()
},
Container::Card(c) => c.appearance(self),
@ -158,12 +170,16 @@ impl container::StyleSheet for Theme {
Container::Pill(c) => c.appearance(self),
Container::Notification(c) => c.appearance(self),
Container::Custom(c) => container::Appearance {
background: (*c).into(),
background: Some((*c).into()),
..container::Appearance::default()
},
Container::QrCode => container::Appearance {
background: color::WHITE.into(),
border_radius: 25.0,
background: Some(color::WHITE.into()),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
},
@ -201,34 +217,46 @@ impl Notification {
match theme {
Theme::Light => match self {
Self::Pending => container::Appearance {
background: color::GREEN.into(),
background: Some(iced::Background::Color(color::GREEN)),
text_color: color::LIGHT_BLACK.into(),
border_width: 1.0,
border_color: color::GREEN,
border_radius: 25.0,
border: iced::Border {
color: color::GREEN,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
Self::Error => container::Appearance {
background: color::ORANGE.into(),
background: Some(iced::Background::Color(color::ORANGE)),
text_color: color::LIGHT_BLACK.into(),
border_width: 1.0,
border_color: color::ORANGE,
border_radius: 25.0,
border: iced::Border {
color: color::ORANGE,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
},
Theme::Dark => match self {
Self::Pending => container::Appearance {
background: color::GREEN.into(),
background: Some(iced::Background::Color(color::GREEN)),
text_color: color::LIGHT_BLACK.into(),
border_width: 1.0,
border_color: color::GREEN,
border_radius: 25.0,
border: iced::Border {
color: color::GREEN,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
Self::Error => container::Appearance {
background: color::ORANGE.into(),
background: Some(iced::Background::Color(color::ORANGE)),
text_color: color::LIGHT_BLACK.into(),
border_width: 1.0,
border_color: color::ORANGE,
border_radius: 25.0,
border: iced::Border {
color: color::ORANGE,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
},
}
@ -250,65 +278,85 @@ impl Card {
match theme {
Theme::Light => match self {
Card::Simple => container::Appearance {
background: color::GREY_2.into(),
background: Some(color::GREY_2.into()),
..container::Appearance::default()
},
Card::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
border_color: color::GREY_2,
border_width: 1.0,
background: Some(iced::Color::TRANSPARENT.into()),
border: iced::Border {
color: color::GREY_2,
width: 1.0,
radius: 10.0.into(),
},
..container::Appearance::default()
},
Card::Invalid => container::Appearance {
background: color::GREY_2.into(),
background: Some(color::GREY_2.into()),
text_color: color::BLACK.into(),
border_width: 1.0,
border_color: color::RED,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 0.0.into(),
},
..container::Appearance::default()
},
Card::Error => container::Appearance {
background: color::GREY_2.into(),
background: Some(color::GREY_2.into()),
text_color: color::RED.into(),
border_width: 1.0,
border_color: color::RED,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 0.0.into(),
},
..container::Appearance::default()
},
Card::Warning => container::Appearance {
background: color::ORANGE.into(),
background: Some(color::ORANGE.into()),
text_color: color::GREY_2.into(),
..container::Appearance::default()
},
},
Theme::Dark => match self {
Card::Simple => container::Appearance {
background: color::GREY_6.into(),
border_radius: 25.0,
background: Some(color::GREY_6.into()),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
Card::Border => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_color: color::GREY_5,
border_width: 1.0,
background: Some(iced::Color::TRANSPARENT.into()),
border: iced::Border {
color: color::GREY_5,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
Card::Invalid => container::Appearance {
background: color::LIGHT_BLACK.into(),
background: Some(color::LIGHT_BLACK.into()),
text_color: color::RED.into(),
border_width: 1.0,
border_radius: 25.0,
border_color: color::RED,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
Card::Error => container::Appearance {
background: color::LIGHT_BLACK.into(),
background: Some(color::LIGHT_BLACK.into()),
text_color: color::RED.into(),
border_width: 1.0,
border_color: color::RED,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 25.0.into(),
},
..container::Appearance::default()
},
Card::Warning => container::Appearance {
background: color::ORANGE.into(),
background: Some(color::ORANGE.into()),
text_color: color::LIGHT_BLACK.into(),
..container::Appearance::default()
},
@ -328,13 +376,21 @@ impl Badge {
fn appearance(&self, _theme: &Theme) -> iced::widget::container::Appearance {
match self {
Self::Standard => container::Appearance {
border_radius: 40.0,
background: color::GREY_4.into(),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 40.0.into(),
},
background: Some(color::GREY_4.into()),
..container::Appearance::default()
},
Self::Bitcoin => container::Appearance {
border_radius: 40.0,
background: color::ORANGE.into(),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 40.0.into(),
},
background: Some(color::ORANGE.into()),
text_color: iced::Color::WHITE.into(),
..container::Appearance::default()
},
@ -355,30 +411,44 @@ impl Pill {
fn appearance(&self, _theme: &Theme) -> iced::widget::container::Appearance {
match self {
Self::Primary => container::Appearance {
background: color::GREEN.into(),
border_radius: 25.0,
background: Some(color::GREEN.into()),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
text_color: color::LIGHT_BLACK.into(),
..container::Appearance::default()
},
Self::Success => container::Appearance {
background: color::GREEN.into(),
border_radius: 25.0,
background: Some(color::GREEN.into()),
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
text_color: color::LIGHT_BLACK.into(),
..container::Appearance::default()
},
Self::Simple => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 1.0,
border_color: color::GREY_3,
background: Some(iced::Color::TRANSPARENT.into()),
border: iced::Border {
color: color::GREY_3,
width: 1.0,
radius: 25.0.into(),
},
text_color: color::GREY_3.into(),
..container::Appearance::default()
},
Self::Warning => container::Appearance {
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 1.0,
border_color: color::RED,
background: Some(iced::Color::TRANSPARENT.into()),
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 25.0.into(),
},
text_color: color::RED.into(),
..container::Appearance::default()
},
}
}
@ -415,24 +485,32 @@ 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::GREY_7,
border_radius: 10.0,
scroller: scrollable::Scroller {
color: color::GREY_7,
border_radius: 10.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
fn active(&self, _style: &Self::Style) -> scrollable::Appearance {
scrollable::Appearance {
gap: None,
container: container::Appearance::default(),
scrollbar: scrollable::Scrollbar {
background: None,
border: iced::Border {
color: color::GREY_3,
width: 0.0,
radius: 10.0.into(),
},
scroller: scrollable::Scroller {
color: color::GREY_7,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 10.0.into(),
},
},
},
}
}
fn hovered(&self, style: &Self::Style, _is_hovered: bool) -> scrollable::Scrollbar {
fn hovered(&self, style: &Self::Style, _is_hovered: bool) -> scrollable::Appearance {
let active = self.active(style);
scrollable::Scrollbar { ..active }
scrollable::Appearance { ..active }
}
}
@ -452,27 +530,33 @@ impl pick_list::StyleSheet for Theme {
placeholder_color: color::GREY_6,
handle_color: color::GREY_6,
background: color::GREEN.into(),
border_width: 1.0,
border_color: color::GREY_7,
border_radius: 25.0,
border: iced::Border {
color: color::GREY_7,
width: 1.0,
radius: 25.0.into(),
},
text_color: iced::Color::BLACK,
},
PickList::Invalid => pick_list::Appearance {
placeholder_color: color::GREY_6,
handle_color: color::GREY_6,
background: color::GREY_6.into(),
border_width: 1.0,
border_color: color::RED,
border_radius: 25.0,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 25.0.into(),
},
text_color: color::RED,
},
PickList::Secondary => pick_list::Appearance {
placeholder_color: color::GREY_3,
handle_color: color::GREY_3,
background: color::TRANSPARENT.into(),
border_width: 1.0,
border_color: color::GREY_3,
border_radius: 25.0,
placeholder_color: color::GREY_6,
handle_color: color::GREY_6,
background: color::GREY_6.into(),
border: iced::Border {
color: color::GREY_7,
width: 1.0,
radius: 25.0.into(),
},
text_color: color::GREY_2,
},
}
@ -493,20 +577,24 @@ impl checkbox::StyleSheet for Theme {
if is_selected {
checkbox::Appearance {
background: color::GREEN.into(),
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
icon_color: color::GREY_4,
text_color: None,
border_radius: 4.0,
border: iced::Border {
color: color::TRANSPARENT,
width: 1.0,
radius: 4.0.into(),
},
}
} else {
checkbox::Appearance {
background: color::GREY_4.into(),
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
icon_color: color::GREEN,
text_color: None,
border_radius: 4.0,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 4.0.into(),
},
}
}
}
@ -538,64 +626,85 @@ impl button::StyleSheet for Theme {
Theme::Dark => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 1.0,
border_color: color::GREY_7,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::GREY_2,
border: iced::Border {
color: color::GREY_7,
width: 1.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Secondary | Button::SecondaryDestructive | Button::Border => {
button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 1.0,
border_color: color::GREY_7,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::GREY_2,
border: iced::Border {
color: color::GREY_7,
width: 1.0,
radius: 25.0.into(),
},
..button::Appearance::default()
}
}
Button::Destructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 1.0,
border_color: color::RED,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::RED,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Transparent => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::GREY_2,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::WHITE,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Menu(active) => {
if *active {
button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::LIGHT_BLACK.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(color::LIGHT_BLACK.into()),
text_color: color::WHITE,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
}
} else {
button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::WHITE,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
}
}
}
@ -609,51 +718,69 @@ impl button::StyleSheet for Theme {
Theme::Dark => match style {
Button::Primary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::GREEN.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(color::GREEN.into()),
text_color: color::LIGHT_BLACK,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Secondary => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::GREEN.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(color::GREEN.into()),
text_color: color::LIGHT_BLACK,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Destructive | Button::SecondaryDestructive => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::RED.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(color::RED.into()),
text_color: color::LIGHT_BLACK,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Transparent => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::GREY_2,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::TransparentBorder | Button::Border => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 25.0,
border_width: 1.0,
border_color: color::GREEN,
background: Some(iced::Color::TRANSPARENT.into()),
text_color: color::WHITE,
border: iced::Border {
color: color::GREEN,
width: 1.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
Button::Menu(_) => button::Appearance {
shadow_offset: iced::Vector::default(),
background: color::LIGHT_BLACK.into(),
border_radius: 25.0,
border_width: 0.0,
border_color: iced::Color::TRANSPARENT,
background: Some(color::LIGHT_BLACK.into()),
text_color: color::WHITE,
border: iced::Border {
color: color::TRANSPARENT,
width: 0.0,
radius: 25.0.into(),
},
..button::Appearance::default()
},
},
}
@ -674,16 +801,20 @@ impl text_input::StyleSheet for Theme {
Form::Simple => text_input::Appearance {
icon_color: color::GREY_7,
background: iced::Background::Color(iced::Color::TRANSPARENT),
border_radius: 25.0,
border_width: 1.0,
border_color: color::GREY_7,
border: iced::Border {
color: color::GREY_7,
width: 1.0,
radius: 25.0.into(),
},
},
Form::Invalid => text_input::Appearance {
icon_color: color::GREY_7,
background: iced::Background::Color(iced::Color::TRANSPARENT),
border_radius: 25.0,
border_width: 1.0,
border_color: color::RED,
border: iced::Border {
color: color::RED,
width: 1.0,
radius: 25.0.into(),
},
},
}
}
@ -729,7 +860,7 @@ impl progress_bar::StyleSheet for Theme {
progress_bar::Appearance {
background: color::GREY_6.into(),
bar: color::GREEN.into(),
border_radius: 10.0,
border_radius: 10.0.into(),
}
}
}
@ -746,7 +877,7 @@ impl slider::StyleSheet for Theme {
let handle = slider::Handle {
shape: slider::HandleShape::Rectangle {
width: 8,
border_radius: 4.0,
border_radius: 4.0.into(),
},
color: color::BLACK,
border_color: color::GREEN,
@ -755,6 +886,7 @@ impl slider::StyleSheet for Theme {
slider::Appearance {
rail: slider::Rail {
colors: (color::GREEN, iced::Color::TRANSPARENT),
border_radius: 4.0.into(),
width: 2.0,
},
handle,
@ -764,7 +896,7 @@ impl slider::StyleSheet for Theme {
let handle = slider::Handle {
shape: slider::HandleShape::Rectangle {
width: 8,
border_radius: 4.0,
border_radius: 4.0.into(),
},
color: color::GREEN,
border_color: color::GREEN,
@ -773,6 +905,7 @@ impl slider::StyleSheet for Theme {
slider::Appearance {
rail: slider::Rail {
colors: (color::GREEN, iced::Color::TRANSPARENT),
border_radius: 4.0.into(),
width: 2.0,
},
handle,
@ -782,7 +915,7 @@ impl slider::StyleSheet for Theme {
let handle = slider::Handle {
shape: slider::HandleShape::Rectangle {
width: 8,
border_radius: 4.0,
border_radius: 4.0.into(),
},
color: color::GREEN,
border_color: color::GREEN,
@ -791,6 +924,7 @@ impl slider::StyleSheet for Theme {
slider::Appearance {
rail: slider::Rail {
colors: (color::GREEN, iced::Color::TRANSPARENT),
border_radius: 4.0.into(),
width: 2.0,
},
handle,
@ -810,3 +944,13 @@ impl svg::StyleSheet for Theme {
svg::Appearance::default()
}
}
impl qr_code::StyleSheet for Theme {
type Style = ();
fn appearance(&self, _style: &Self::Style) -> qr_code::Appearance {
qr_code::Appearance {
cell: color::BLACK,
background: color::WHITE,
}
}
}

View File

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