ui: add support for legacy

This commit is contained in:
edouard 2023-03-24 17:37:13 +01:00
parent f3bf6fa589
commit 8d337eb7e1
17 changed files with 1416 additions and 7 deletions

View File

@ -1,8 +1,8 @@
mod section;
use iced::widget::{button, column, container, radio, row, text, Column, Space};
use iced::widget::{button, column, container, radio, row, text, Space};
use iced::{executor, Application, Command, Length, Settings, Subscription};
use liana_ui::{theme, widget::Element};
use liana_ui::{theme, widget::*};
pub fn main() -> iced::Result {
DesignSystem::run(Settings::with_flags(Config {}))
@ -21,6 +21,7 @@ struct DesignSystem {
pub enum ThemeType {
Light,
Dark,
Legacy,
}
#[derive(Debug, Clone)]
@ -77,6 +78,7 @@ impl Application for DesignSystem {
self.theme = match theme {
ThemeType::Light => theme::Theme::Light,
ThemeType::Dark => theme::Theme::Dark,
ThemeType::Legacy => theme::Theme::Legacy,
}
}
Message::Section(i) => {
@ -118,6 +120,7 @@ impl Application for DesignSystem {
Some(match self.theme {
theme::Theme::Light => ThemeType::Light,
theme::Theme::Dark => ThemeType::Dark,
theme::Theme::Legacy => ThemeType::Legacy,
}),
Message::ThemeChanged,
))

View File

@ -3,7 +3,7 @@ use iced::{
widget::{button, column, container, row, Space},
Alignment, Length,
};
use liana_ui::{color, text::*, theme, widget::Element};
use liana_ui::{color, component::text::*, theme, widget::Element};
use super::{Message, Section};

View File

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

View File

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

View File

@ -0,0 +1,40 @@
use crate::{theme, widget::*};
use iced::widget::{button, container, row};
use iced::{Alignment, Length};
use super::text::text;
pub fn alert<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Destructive.into())
}
pub fn primary<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Primary.into())
}
pub fn transparent<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Transparent.into())
}
pub fn border<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button::Button::new(content(icon, t)).style(theme::Button::Secondary.into())
}
pub fn transparent_border<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Button<'a, T> {
button(content(icon, t)).style(theme::Button::TransparentBorder.into())
}
fn content<'a, T: 'a>(icon: Option<Text<'a>>, t: &'static str) -> Container<'a, T> {
match icon {
None => container(text(t)).width(Length::Fill).center_x().padding(5),
Some(i) => container(
row![i, text(t)]
.spacing(10)
.width(iced::Length::Fill)
.align_items(Alignment::Center),
)
.width(iced::Length::Fill)
.center_x()
.padding(5),
}
}

View File

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

View File

@ -0,0 +1,83 @@
use crate::widget::*;
use iced::widget::column;
use iced_lazy::{self, Component};
use std::marker::PhantomData;
pub struct Collapse<'a, M, H, F, C> {
before: H,
after: F,
content: C,
phantom: PhantomData<&'a M>,
}
impl<'a, Message, T, H, F, C> Collapse<'a, Message, H, F, C>
where
Message: 'a,
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>> + 'a,
F: Fn() -> Button<'a, Event<T>> + 'a,
C: Fn() -> Element<'a, T> + 'a,
{
pub fn new(before: H, after: F, content: C) -> Self {
Collapse {
before,
after,
content,
phantom: PhantomData,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Event<T> {
Internal(T),
Collapse(bool),
}
impl<'a, Message, T, H, F, C> Component<Message, iced::Renderer<crate::theme::Theme>>
for Collapse<'a, Message, H, F, C>
where
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>>,
F: Fn() -> Button<'a, Event<T>>,
C: Fn() -> Element<'a, T>,
{
type State = bool;
type Event = Event<T>;
fn update(&mut self, state: &mut Self::State, event: Event<T>) -> Option<Message> {
match event {
Event::Internal(e) => Some(e.into()),
Event::Collapse(s) => {
*state = s;
None
}
}
}
fn view(&self, state: &Self::State) -> Element<Self::Event> {
if *state {
column![
(self.after)().on_press(Event::Collapse(false)),
(self.content)().map(Event::Internal)
]
.into()
} else {
column![(self.before)().on_press(Event::Collapse(true))].into()
}
}
}
impl<'a, Message, T, H: 'a, F: 'a, C: 'a> From<Collapse<'a, Message, H, F, C>>
for Element<'a, Message>
where
Message: 'a,
T: Into<Message> + Clone + 'a,
H: Fn() -> Button<'a, Event<T>>,
F: Fn() -> Button<'a, Event<T>>,
C: Fn() -> Element<'a, T>,
{
fn from(c: Collapse<'a, Message, H, F, C>) -> Self {
iced_lazy::component(c)
}
}

View File

@ -0,0 +1,87 @@
use iced::{widget::text_input, Length};
use crate::{color, component::text::*, theme, util::Collection, widget::*};
#[derive(Debug, Clone)]
pub struct Value<T> {
pub value: T,
pub valid: bool,
}
impl std::default::Default for Value<String> {
fn default() -> Self {
Self {
value: "".to_string(),
valid: true,
}
}
}
pub struct Form<'a, Message> {
input: text_input::TextInput<'a, Message, iced::Renderer<theme::Theme>>,
warning: Option<&'a str>,
valid: bool,
}
impl<'a, Message: 'a> Form<'a, Message>
where
Message: Clone,
{
/// Creates a new [`Form`].
///
/// It expects:
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`Form`] changes
pub fn new<F>(placeholder: &str, value: &Value<String>, on_change: F) -> Self
where
F: 'static + Fn(String) -> Message,
{
Self {
input: text_input::TextInput::new(placeholder, &value.value, on_change),
warning: None,
valid: value.valid,
}
}
/// Sets the [`Form`] with a warning message
pub fn warning(mut self, warning: &'a str) -> Self {
self.warning = Some(warning);
self
}
/// Sets the padding of the [`Form`].
pub fn padding(mut self, units: u16) -> Self {
self.input = self.input.padding(units);
self
}
/// Sets the [`Form`] with a text size
pub fn size(mut self, size: u16) -> Self {
self.input = self.input.size(size);
self
}
}
impl<'a, Message: 'a + Clone> From<Form<'a, Message>> for Element<'a, Message> {
fn from(form: Form<'a, Message>) -> Element<'a, Message> {
Container::new(
Column::new()
.push(if !form.valid {
form.input.style(theme::Form::Invalid)
} else {
form.input
})
.push_maybe(if !form.valid {
form.warning
.map(|message| text(message).style(color::legacy::ALERT).small())
} else {
None
})
.width(Length::Fill)
.spacing(5),
)
.width(Length::Fill)
.into()
}
}

View File

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

View File

@ -0,0 +1,279 @@
/// modal widget from https://github.com/iced-rs/iced/blob/master/examples/modal/
use iced_native::alignment::Alignment;
use iced_native::widget::{self, Tree};
use iced_native::{
event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length,
Point, Rectangle, Shell, Size, Widget,
};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
Self {
base: base.into(),
modal: modal.into(),
on_blur: None,
}
}
/// Sets the message that will be produces when the background
/// of the [`Modal`] is pressed
pub fn on_blur(self, on_blur: Option<Message>) -> Self {
Self { on_blur, ..self }
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.base
.as_widget()
.operate(&mut state.children[0], layout, renderer, operation);
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event {
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
return event::Status::Captured;
}
}
}
self.content.as_widget_mut().on_event(
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor_position,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
renderer,
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
Element::new(modal)
}
}

View File

@ -0,0 +1,49 @@
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();
Container::new(Container::new(collapse::Collapse::new(
move || {
Button::new(
Row::new()
.push(
Container::new(text(message_clone.to_string()).small().bold())
.width(Length::Fill),
)
.push(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Learn more").small().bold())
.push(icon::collapse_icon()),
),
)
.style(theme::Button::Transparent)
},
move || {
Button::new(
Row::new()
.push(
Container::new(text(message.to_owned()).small().bold()).width(Length::Fill),
)
.push(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Learn more").small().bold())
.push(icon::collapsed_icon()),
),
)
.style(theme::Button::Transparent)
},
move || Element::<'a, T>::from(text(error.to_owned()).small()),
)))
.padding(15)
.style(theme::Container::Card(theme::Card::Warning))
.width(Length::Fill)
}

View File

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

233
gui/ui/src/icon.rs Normal file
View File

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

View File

@ -1,7 +1,9 @@
pub mod color;
pub mod component;
pub mod font;
pub mod text;
pub mod icon;
pub mod theme;
pub mod util;
pub mod widget {
#![allow(dead_code)]
@ -10,5 +12,9 @@ pub mod widget {
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>;
}

View File

@ -1,6 +1,6 @@
use iced::{
application,
widget::{button, container, radio, text},
widget::{button, container, radio, text, text_input},
};
use super::color;
@ -10,6 +10,7 @@ pub enum Theme {
#[default]
Dark,
Light,
Legacy,
}
impl application::StyleSheet for Theme {
@ -25,6 +26,10 @@ impl application::StyleSheet for Theme {
background_color: color::LIGHT_BLACK,
text_color: color::LIGHT_GREY,
},
Theme::Legacy => application::Appearance {
background_color: color::legacy::BACKGROUND,
text_color: color::BLACK,
},
}
}
}
@ -60,6 +65,9 @@ pub enum Container {
Background,
Foreground,
Border,
Card(Card),
Badge(Badge),
Pill(Pill),
Custom(iced::Color),
}
@ -86,6 +94,9 @@ impl container::StyleSheet for Theme {
border_color: color::LIGHT_BLACK.into(),
..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()
@ -110,11 +121,204 @@ impl container::StyleSheet for Theme {
border_color: color::LIGHT_GREY.into(),
..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.into(),
..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()
},
},
}
}
}
#[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,
..container::Appearance::default()
},
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,
..container::Appearance::default()
},
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()
},
}
}
}
@ -151,6 +355,7 @@ pub enum Button {
Secondary,
Destructive,
Transparent,
TransparentBorder,
}
impl button::StyleSheet for Theme {
@ -183,7 +388,7 @@ impl button::StyleSheet for Theme {
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_GREY,
},
Button::Transparent => button::Appearance {
Button::Transparent | Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
@ -217,7 +422,7 @@ impl button::StyleSheet for Theme {
border_color: iced::Color::TRANSPARENT,
text_color: color::LIGHT_BLACK,
},
Button::Transparent => button::Appearance {
Button::Transparent | Button::TransparentBorder => button::Appearance {
shadow_offset: iced::Vector::default(),
background: iced::Color::TRANSPARENT.into(),
border_radius: 10.0,
@ -226,6 +431,40 @@ impl button::StyleSheet for Theme {
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,
},
},
}
}
@ -264,6 +503,14 @@ impl button::StyleSheet for Theme {
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 {
@ -298,7 +545,102 @@ impl button::StyleSheet for Theme {
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)
}
}

25
gui/ui/src/util.rs Normal file
View File

@ -0,0 +1,25 @@
/// 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)
}
}