first_push
This commit is contained in:
commit
ca41081e8f
18 changed files with 5559 additions and 0 deletions
4215
Cargo.lock
generated
Normal file
4215
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
29
Cargo.toml
Normal file
29
Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "iced-material"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
palette = "*"
|
||||
freedesktop-icons = "*"
|
||||
|
||||
[dependencies.iced]
|
||||
#git = "https://github.com/ibaryshnikov/iced.git"
|
||||
#rev = "901bbeb"
|
||||
features = ["wgpu","lazy","svg","advanced"]
|
||||
path = "../../iced"
|
||||
|
||||
[dependencies.iced_winit]
|
||||
#git = "https://github.com/ibaryshnikov/iced.git"
|
||||
#rev = "901bbeb"
|
||||
path = "../../iced/winit"
|
||||
|
||||
|
||||
[dependencies.iced_wgpu]
|
||||
#git = "https://github.com/ibaryshnikov/iced.git"
|
||||
#rev = "901bbeb"
|
||||
path = "../../iced/wgpu"
|
||||
|
||||
#iced = {git="https://github.com/iced-rs/iced",features=["lazy","svg","advanced"]}
|
||||
71
src/header.rs
Normal file
71
src/header.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use crate::iced_material;
|
||||
use crate::icon;
|
||||
use iced::{
|
||||
alignment::Alignment,
|
||||
widget::{button, container, row, text, Space},
|
||||
Element, Length, Padding,
|
||||
};
|
||||
//pub const fun stri
|
||||
pub fn header<'a, Message, Renderer>(
|
||||
side_button: Message,
|
||||
back_message: Message,
|
||||
settings_button: Message,
|
||||
exit_button: Message,
|
||||
//page: &Page,
|
||||
title: &str,
|
||||
) -> Element<'a, Message, crate::theme::Theme, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
//Theme: 'a + text::Catalog + button::Catalog + svg::Catalog + container::Catalog,
|
||||
Renderer: 'a + iced::advanced::text::Renderer + iced::advanced::svg::Renderer,
|
||||
{
|
||||
container(
|
||||
row![
|
||||
row!(
|
||||
button(icon!("panel-left"))
|
||||
.padding(4)
|
||||
.width(30)
|
||||
.height(30)
|
||||
.on_press(side_button),
|
||||
button(icon!("chevron-left"))
|
||||
.padding(4)
|
||||
.width(30)
|
||||
.height(30)
|
||||
.on_press(back_message),
|
||||
)
|
||||
.spacing(10),
|
||||
Space::with_width(Length::Fill),
|
||||
text!("{}", title),
|
||||
Space::with_width(Length::Fill),
|
||||
row!(
|
||||
button(icon!("settings"))
|
||||
.padding(4)
|
||||
.on_press(settings_button)
|
||||
.width(30)
|
||||
.height(30),
|
||||
button(icon!("x"))
|
||||
.padding(4)
|
||||
.on_press(exit_button)
|
||||
.width(30)
|
||||
.height(30)
|
||||
)
|
||||
.spacing(10),
|
||||
]
|
||||
.padding({
|
||||
if cfg!(target_os = "android") {
|
||||
Padding {
|
||||
top: 40.,
|
||||
left: 10.,
|
||||
right: 10.,
|
||||
bottom: 10.,
|
||||
}
|
||||
} else {
|
||||
Padding::new(10.)
|
||||
}
|
||||
})
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(10),
|
||||
)
|
||||
.style(crate::theme::container::grey)
|
||||
.into()
|
||||
}
|
||||
19
src/lib.rs
Normal file
19
src/lib.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use iced::widget::{svg, Svg};
|
||||
|
||||
pub mod header;
|
||||
pub mod sidebar;
|
||||
pub mod theme;
|
||||
|
||||
pub fn svg_icon<'a>(bytes: &'static [u8]) -> Svg<'a, theme::Theme> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
}
|
||||
use crate as iced_material;
|
||||
#[macro_export]
|
||||
macro_rules! icon {
|
||||
($message_id:literal) => {{
|
||||
iced_material::svg_icon(include_bytes!(concat!(
|
||||
concat!("/home/me/workspace/lucide/icons/", $message_id),
|
||||
".svg"
|
||||
)))
|
||||
}};
|
||||
}
|
||||
51
src/sidebar.rs
Normal file
51
src/sidebar.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use iced::{
|
||||
alignment::Alignment,
|
||||
widget::{button, column, container, scrollable, text},
|
||||
Element, Length,
|
||||
};
|
||||
pub fn sidebar<'a, Message, Renderer>(
|
||||
names: &[&str],
|
||||
f: impl Fn(u8) -> Message + 'a,
|
||||
) -> Element<'a, Message, crate::theme::Theme, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
// Theme: 'a
|
||||
// + text::Catalog
|
||||
// + button::Catalog
|
||||
// + svg::Catalog
|
||||
// + container::Catalog
|
||||
// + scrollable::Catalog,
|
||||
Renderer: 'a + iced::advanced::text::Renderer,
|
||||
{
|
||||
let options: Vec<Element<'_, _, _, _>> = names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, title)| {
|
||||
// hey
|
||||
button(container(text!("{}", title)).center_y(Length::Fill))
|
||||
.width(Length::Fill)
|
||||
.height(50)
|
||||
.on_press(f(i as u8))
|
||||
// .style(move |theme, status| {
|
||||
// if i as u8 == self.selected {
|
||||
// text_button(theme, button::Status::Pressed)
|
||||
// } else {
|
||||
// text_button(theme, status)
|
||||
// }
|
||||
// })
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
container(scrollable(
|
||||
column(options)
|
||||
.padding(10)
|
||||
.align_x(Alignment::Center)
|
||||
.spacing(10),
|
||||
))
|
||||
.height(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.style(crate::theme::container::light_grey)
|
||||
.into()
|
||||
//
|
||||
}
|
||||
366
src/theme/button.rs
Normal file
366
src/theme/button.rs
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
use iced::widget::button::{Catalog, Status, Style, StyleFn};
|
||||
use iced::{Background, Border, Color};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(text_button)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_button(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: Some(Background::Color(theme.colors().text.high_alpha)),
|
||||
text_color: theme.colors().text.base,
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => Style {
|
||||
background: Some(Background::Color(theme.colors().text.med_alpha)),
|
||||
text_color: theme.colors().text.base,
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Disabled => {
|
||||
let active = text_button(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_button(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: Some(Background::Color(theme.colors().text.high_alpha)),
|
||||
text_color: theme.colors().text.base,
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
width: 0.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => Style {
|
||||
background: Some(Background::Color(theme.colors().text.med_alpha)),
|
||||
text_color: theme.colors().text.base,
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
width: 0.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Disabled => {
|
||||
let active = text_button(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
width: 0.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unselected(theme: &Theme, _status: Status) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.dark)),
|
||||
text_color: theme.colors().text.base,
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(theme: &Theme, _status: Status) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.darkest)),
|
||||
text_color: theme.colors().text.base,
|
||||
border: Border {
|
||||
radius: 10.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn secondary(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: Some(Background::Color(theme.colors().accent.high_alpha)),
|
||||
text_color: theme.colors().accent.base,
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => Style {
|
||||
background: Some(Background::Color(theme.colors().accent.med_alpha)),
|
||||
text_color: theme.colors().accent.base,
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Disabled => {
|
||||
let active = secondary(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tertiary(theme: &Theme, status: Status, selected: bool) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: Some(Background::Color(if selected {
|
||||
theme.colors().action.med_alpha
|
||||
} else {
|
||||
theme.colors().background.dark
|
||||
})),
|
||||
border: Border {
|
||||
color: if selected {
|
||||
theme.colors().action.low_alpha
|
||||
} else if theme.colors().is_dark_theme() {
|
||||
theme.colors().background.lightest
|
||||
} else {
|
||||
theme.colors().background.darkest
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => {
|
||||
let active = tertiary(theme, Status::Active, selected);
|
||||
|
||||
Style {
|
||||
background: Some(Background::Color(if selected {
|
||||
theme.colors().action.high_alpha
|
||||
} else if theme.colors().is_dark_theme() {
|
||||
theme.colors().background.light
|
||||
} else {
|
||||
theme.colors().background.darker
|
||||
})),
|
||||
..active
|
||||
}
|
||||
}
|
||||
Status::Disabled => {
|
||||
let active = tertiary(theme, Status::Active, selected);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => {
|
||||
let active = context(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.darker)),
|
||||
..active
|
||||
}
|
||||
}
|
||||
Status::Disabled => {
|
||||
let active = context(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bare(_theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed | Status::Hovered => Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
..Default::default()
|
||||
},
|
||||
Status::Disabled => {
|
||||
let active = bare(_theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn side_menu(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: None,
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => {
|
||||
let active = side_menu(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.dark)),
|
||||
..active
|
||||
}
|
||||
}
|
||||
Status::Disabled => {
|
||||
let active = side_menu(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn side_menu_selected(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
Status::Active | Status::Pressed => Style {
|
||||
background: Some(Background::Color(theme.colors().background.darker)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Status::Hovered => {
|
||||
let active = side_menu_selected(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.darkest)),
|
||||
..active
|
||||
}
|
||||
}
|
||||
Status::Disabled => {
|
||||
let active = side_menu_selected(theme, Status::Active);
|
||||
|
||||
Style {
|
||||
text_color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
border: Border {
|
||||
color: Color {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/theme/checkbox.rs
Normal file
33
src/theme/checkbox.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use iced::{
|
||||
widget::checkbox::{Catalog, Status, Style, StyleFn},
|
||||
Border,
|
||||
};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: iced::widget::checkbox::Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme, status: Status) -> Style {
|
||||
match status {
|
||||
_ => Style {
|
||||
background: iced::Background::Color(theme.colors().background.base),
|
||||
icon_color: theme.colors().accent.base,
|
||||
border: Border {
|
||||
color: theme.colors().accent.base,
|
||||
width: 1.0,
|
||||
radius: 2.into(),
|
||||
},
|
||||
text_color: Some(theme.colors().text.base),
|
||||
},
|
||||
}
|
||||
}
|
||||
147
src/theme/container.rs
Normal file
147
src/theme/container.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use iced::widget::container::{transparent, Catalog, Style, StyleFn};
|
||||
use iced::{Background, Border, Color};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(transparent)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grey(theme: &Theme) -> Style {
|
||||
let background = theme.colors().background.darker;
|
||||
|
||||
Style {
|
||||
background: Some(Background::Color(background)),
|
||||
text_color: Some(theme.colors().text.base),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn light_grey(theme: &Theme) -> Style {
|
||||
let background = theme.colors().background.dark;
|
||||
|
||||
Style {
|
||||
background: Some(Background::Color(background)),
|
||||
text_color: Some(theme.colors().text.base),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.base)),
|
||||
text_color: Some(theme.colors().text.base),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pane_body(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.dark)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 1.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pane_body_selected(theme: &Theme) -> Style {
|
||||
let pane_body = pane_body(theme);
|
||||
|
||||
Style {
|
||||
border: Border {
|
||||
color: theme.colors().action.base,
|
||||
..pane_body.border
|
||||
},
|
||||
..pane_body
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(_theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_selected(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.darker)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(theme: &Theme) -> Style {
|
||||
Style {
|
||||
//TODO: Blur background when possible?
|
||||
background: Some(Background::Color(theme.colors().background.base)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 1.0,
|
||||
color: theme.colors().background.darker,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().info.high_alpha)),
|
||||
border: Border {
|
||||
radius: 0.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn semi_transparent(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(
|
||||
Color {
|
||||
a: 0.80,
|
||||
..theme.colors().background.base
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_banner(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.dark)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 1.0,
|
||||
color: theme.colors().background.lighter,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_modal(theme: &Theme) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(theme.colors().background.dark)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 1.0,
|
||||
color: theme.colors().error.base,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
200
src/theme/data.rs
Normal file
200
src/theme/data.rs
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
use iced::Color;
|
||||
use palette::rgb::Rgb;
|
||||
use palette::{DarkenAssign, FromColor, LightenAssign, Mix, Okhsl, Srgb};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ColorTheme {
|
||||
pub name: String,
|
||||
pub colors: Colors,
|
||||
}
|
||||
|
||||
impl ColorTheme {
|
||||
pub fn new(name: String, palette: &Palette) -> Self {
|
||||
ColorTheme {
|
||||
name,
|
||||
colors: Colors::new(palette),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ColorTheme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "Light".to_string(),
|
||||
colors: Colors::new(&Palette::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Colors {
|
||||
pub background: Subpalette,
|
||||
pub text: Subpalette,
|
||||
pub action: Subpalette,
|
||||
pub accent: Subpalette,
|
||||
pub alert: Subpalette,
|
||||
pub error: Subpalette,
|
||||
pub info: Subpalette,
|
||||
pub success: Subpalette,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
pub fn new(palette: &Palette) -> Self {
|
||||
Colors {
|
||||
background: Subpalette::from_color(palette.background, palette),
|
||||
text: Subpalette::from_color(palette.text, palette),
|
||||
action: Subpalette::from_color(palette.action, palette),
|
||||
accent: Subpalette::from_color(palette.accent, palette),
|
||||
alert: Subpalette::from_color(palette.alert, palette),
|
||||
error: Subpalette::from_color(palette.error, palette),
|
||||
info: Subpalette::from_color(palette.info, palette),
|
||||
success: Subpalette::from_color(palette.success, palette),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dark_theme(&self) -> bool {
|
||||
self.background.is_dark()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subpalette {
|
||||
pub base: Color,
|
||||
pub light: Color,
|
||||
pub lighter: Color,
|
||||
pub lightest: Color,
|
||||
pub dark: Color,
|
||||
pub darker: Color,
|
||||
pub darkest: Color,
|
||||
pub low_alpha: Color,
|
||||
pub med_alpha: Color,
|
||||
pub high_alpha: Color,
|
||||
}
|
||||
|
||||
impl Subpalette {
|
||||
pub fn from_color(color: Color, palette: &Palette) -> Subpalette {
|
||||
let is_dark = is_dark(palette.background);
|
||||
|
||||
Subpalette {
|
||||
base: color,
|
||||
light: lighten(color, 0.03),
|
||||
lighter: lighten(color, 0.06),
|
||||
lightest: lighten(color, 0.12),
|
||||
dark: darken(color, 0.03),
|
||||
darker: darken(color, 0.06),
|
||||
darkest: darken(color, 0.12),
|
||||
low_alpha: if is_dark {
|
||||
alpha(color, 0.4)
|
||||
} else {
|
||||
alpha(color, 0.8)
|
||||
},
|
||||
med_alpha: if is_dark {
|
||||
alpha(color, 0.2)
|
||||
} else {
|
||||
alpha(color, 0.4)
|
||||
},
|
||||
high_alpha: if is_dark {
|
||||
alpha(color, 0.1)
|
||||
} else {
|
||||
alpha(color, 0.3)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dark(&self) -> bool {
|
||||
is_dark(self.base)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Palette {
|
||||
pub background: Color,
|
||||
pub text: Color,
|
||||
pub action: Color,
|
||||
pub accent: Color,
|
||||
pub alert: Color,
|
||||
pub error: Color,
|
||||
pub info: Color,
|
||||
pub success: Color,
|
||||
}
|
||||
|
||||
impl Default for Palette {
|
||||
fn default() -> Palette {
|
||||
Palette {
|
||||
background: hex_to_color("#2b292d").unwrap(),
|
||||
text: hex_to_color("#fecdb2").unwrap(),
|
||||
action: hex_to_color("#b1b695").unwrap(),
|
||||
accent: hex_to_color("#d1d1e0").unwrap(),
|
||||
alert: hex_to_color("#ffa07a").unwrap(),
|
||||
error: hex_to_color("#e06b75").unwrap(),
|
||||
info: hex_to_color("#f5d76e").unwrap(),
|
||||
success: hex_to_color("#b1b695").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hex_to_color(hex: &str) -> Option<Color> {
|
||||
if hex.len() == 7 {
|
||||
let hash = &hex[0..1];
|
||||
let r = u8::from_str_radix(&hex[1..3], 16);
|
||||
let g = u8::from_str_radix(&hex[3..5], 16);
|
||||
let b = u8::from_str_radix(&hex[5..7], 16);
|
||||
|
||||
return match (hash, r, g, b) {
|
||||
("#", Ok(r), Ok(g), Ok(b)) => Some(Color {
|
||||
r: r as f32 / 255.0,
|
||||
g: g as f32 / 255.0,
|
||||
b: b as f32 / 255.0,
|
||||
a: 1.0,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
pub fn is_dark(color: Color) -> bool {
|
||||
to_hsl(color).lightness < 0.5
|
||||
}
|
||||
|
||||
pub fn to_hsl(color: Color) -> Okhsl {
|
||||
let mut hsl = Okhsl::from_color(Srgb::new(color.r, color.g, color.b));
|
||||
if hsl.saturation.is_nan() {
|
||||
hsl.saturation = Okhsl::max_saturation();
|
||||
}
|
||||
|
||||
hsl
|
||||
}
|
||||
|
||||
pub fn from_hsl(hsl: Okhsl) -> Color {
|
||||
let color = Srgb::from_color(hsl);
|
||||
Color::from_rgb(color.red, color.green, color.blue)
|
||||
}
|
||||
|
||||
pub fn alpha(color: Color, alpha: f32) -> Color {
|
||||
Color { a: alpha, ..color }
|
||||
}
|
||||
|
||||
pub fn mix(a: Color, b: Color, factor: f32) -> Color {
|
||||
let a_hsl = to_hsl(a);
|
||||
let b_hsl = to_hsl(b);
|
||||
|
||||
let mixed = a_hsl.mix(b_hsl, factor);
|
||||
from_hsl(mixed)
|
||||
}
|
||||
|
||||
pub fn lighten(color: Color, amount: f32) -> Color {
|
||||
let mut hsl = to_hsl(color);
|
||||
|
||||
hsl.lighten_fixed_assign(amount);
|
||||
|
||||
from_hsl(hsl)
|
||||
}
|
||||
|
||||
pub fn darken(color: Color, amount: f32) -> Color {
|
||||
let mut hsl = to_hsl(color);
|
||||
|
||||
hsl.darken_fixed_assign(amount);
|
||||
|
||||
from_hsl(hsl)
|
||||
}
|
||||
33
src/theme/menu.rs
Normal file
33
src/theme/menu.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
pub use iced::widget::overlay::menu::Style;
|
||||
use iced::{
|
||||
widget::overlay::menu::{Catalog, StyleFn},
|
||||
Background, Border,
|
||||
};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> StyleFn<'a, Self> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &StyleFn<'_, Self>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
text_color: theme.colors().text.base,
|
||||
background: Background::Color(theme.colors().background.darker),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
color: theme.colors().background.darker,
|
||||
},
|
||||
selected_text_color: theme.colors().text.low_alpha,
|
||||
selected_background: Background::Color(theme.colors().background.dark),
|
||||
}
|
||||
}
|
||||
100
src/theme/mod.rs
Normal file
100
src/theme/mod.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
pub mod button;
|
||||
pub mod checkbox;
|
||||
pub mod container;
|
||||
pub mod data;
|
||||
pub mod menu;
|
||||
pub mod pick_list;
|
||||
pub mod scrollable;
|
||||
pub mod svg;
|
||||
pub mod text;
|
||||
pub mod text_editor;
|
||||
pub mod text_input;
|
||||
pub mod toggler;
|
||||
use data::ColorTheme;
|
||||
use data::Colors;
|
||||
use iced::color;
|
||||
// TODO: If we use non-standard font sizes, we should consider
|
||||
// Config.font.size since it's user configurable
|
||||
pub const TEXT_SIZE: f32 = 13.0;
|
||||
pub const ICON_SIZE: f32 = 12.0;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Theme {
|
||||
Selected(ColorTheme),
|
||||
Preview {
|
||||
selected: ColorTheme,
|
||||
preview: ColorTheme,
|
||||
},
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn light() -> Self {
|
||||
let palette = data::Palette {
|
||||
accent: color!(0x1c71d8),
|
||||
success: color!(0x1b8553),
|
||||
error: color!(0xc01c28),
|
||||
background: color!(0xfafafa),
|
||||
alert: color!(0x9c6e03),
|
||||
text: color!(0x000000),
|
||||
//
|
||||
action: color!(0x62a0ea),
|
||||
info: color!(0x1b8553),
|
||||
};
|
||||
Theme::Selected(ColorTheme {
|
||||
name: String::from("light"),
|
||||
colors: Colors::new(&palette),
|
||||
})
|
||||
}
|
||||
pub fn dark() -> Self {
|
||||
let palette = data::Palette {
|
||||
accent: color!(0x78aeed),
|
||||
success: color!(0x8ff0a4),
|
||||
error: color!(0xff7b63),
|
||||
background: color!(0x242424),
|
||||
alert: color!(0xf8e45c),
|
||||
text: color!(0xffffff),
|
||||
//
|
||||
action: color!(0x99c1f1),
|
||||
info: color!(0x1b8553),
|
||||
};
|
||||
Theme::Selected(ColorTheme {
|
||||
name: String::from("light"),
|
||||
colors: Colors::new(&palette),
|
||||
})
|
||||
}
|
||||
pub fn preview(&self, theme: ColorTheme) -> Self {
|
||||
match self {
|
||||
Theme::Selected(selected) | Theme::Preview { selected, .. } => Self::Preview {
|
||||
selected: selected.clone(),
|
||||
preview: theme,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Self {
|
||||
match self {
|
||||
Theme::Selected(selected) | Theme::Preview { selected, .. } => {
|
||||
Self::Selected(selected.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn colors(&self) -> &Colors {
|
||||
match self {
|
||||
Theme::Selected(selected) => &selected.colors,
|
||||
Theme::Preview { preview, .. } => &preview.colors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorTheme> for Theme {
|
||||
fn from(theme: ColorTheme) -> Self {
|
||||
Theme::Selected(theme)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Self::from(ColorTheme::default())
|
||||
}
|
||||
}
|
||||
26
src/theme/pick_list.rs
Normal file
26
src/theme/pick_list.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use iced::widget::pick_list::{Catalog, Status, Style, StyleFn};
|
||||
use iced::{Background, Border};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> StyleFn<'a, Self> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &StyleFn<'_, Self>, status: Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme, _status: Status) -> Style {
|
||||
Style {
|
||||
text_color: theme.colors().text.base,
|
||||
placeholder_color: theme.colors().background.dark,
|
||||
handle_color: theme.colors().background.darkest,
|
||||
background: Background::Color(theme.colors().background.darker),
|
||||
border: Border::default().rounded(5),
|
||||
}
|
||||
}
|
||||
87
src/theme/scrollable.rs
Normal file
87
src/theme/scrollable.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use iced::{
|
||||
widget::{
|
||||
container,
|
||||
scrollable::{Catalog, Rail, Scrollbar, Scroller, Status, Style, StyleFn},
|
||||
},
|
||||
Background, Border, Color, Shadow,
|
||||
};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme, status: Status) -> Style {
|
||||
let rail = Rail {
|
||||
background: None,
|
||||
border: Border::default(),
|
||||
scroller: Scroller {
|
||||
color: theme.colors().background.darker,
|
||||
border: Border {
|
||||
radius: 8.0.into(),
|
||||
width: 0.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
match status {
|
||||
Status::Active { .. } | Status::Hovered { .. } | Status::Dragged { .. } => Style {
|
||||
container: container::Style {
|
||||
text_color: None,
|
||||
background: None,
|
||||
border: Border {
|
||||
radius: 8.0.into(),
|
||||
width: 1.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
},
|
||||
vertical_rail: rail,
|
||||
horizontal_rail: rail,
|
||||
gap: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hidden(_theme: &Theme, status: Status) -> Style {
|
||||
let rail = Rail {
|
||||
background: None,
|
||||
border: Border::default(),
|
||||
scroller: Scroller {
|
||||
color: Color::TRANSPARENT,
|
||||
border: Border {
|
||||
radius: 0.0.into(),
|
||||
width: 0.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
match status {
|
||||
Status::Active { .. } | Status::Hovered { .. } | Status::Dragged { .. } => Style {
|
||||
container: container::Style {
|
||||
text_color: None,
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
border: Border {
|
||||
radius: 8.0.into(),
|
||||
width: 1.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
},
|
||||
vertical_rail: rail,
|
||||
horizontal_rail: rail,
|
||||
gap: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
31
src/theme/svg.rs
Normal file
31
src/theme/svg.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#[allow(unused_variables)]
|
||||
use super::Theme;
|
||||
use iced::widget::svg::{Catalog, Status, Style, StyleFn};
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(_theme: &Theme, _status: Status) -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
pub fn error(theme: &Theme, _status: Status) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().error.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn success(theme: &Theme, _status: Status) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().success.base),
|
||||
}
|
||||
}
|
||||
67
src/theme/text.rs
Normal file
67
src/theme/text.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use iced::widget::text::{Catalog, Style, StyleFn};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(none)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none(_theme: &Theme) -> Style {
|
||||
Style { color: None }
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().text.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accent(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().accent.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alert(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().alert.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn info(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().info.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().error.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn success(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().success.base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transparent(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().text.low_alpha),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transparent_accent(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.colors().accent.low_alpha),
|
||||
}
|
||||
}
|
||||
29
src/theme/text_editor.rs
Normal file
29
src/theme/text_editor.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use iced::{
|
||||
widget::text_editor::{Catalog, Status, Style, StyleFn},
|
||||
Border,
|
||||
};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(none)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none(_theme: &Theme, _state: Status) -> Style {
|
||||
Style {
|
||||
value: _theme.colors().text.base,
|
||||
selection: _theme.colors().text.light,
|
||||
icon: _theme.colors().text.base,
|
||||
placeholder: _theme.colors().text.lighter,
|
||||
border: Border::default().rounded(5.),
|
||||
background: iced::Background::from(_theme.colors().background.darker),
|
||||
}
|
||||
}
|
||||
29
src/theme/text_input.rs
Normal file
29
src/theme/text_input.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use iced::{
|
||||
widget::text_input::{Catalog, Status, Style, StyleFn},
|
||||
Border,
|
||||
};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(none)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none(_theme: &Theme, _state: Status) -> Style {
|
||||
Style {
|
||||
value: _theme.colors().text.base,
|
||||
selection: _theme.colors().text.light,
|
||||
icon: _theme.colors().text.base,
|
||||
placeholder: _theme.colors().text.lighter,
|
||||
border: Border::default().rounded(10.),
|
||||
background: iced::Background::from(_theme.colors().background.base),
|
||||
}
|
||||
}
|
||||
26
src/theme/toggler.rs
Normal file
26
src/theme/toggler.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use iced::widget::toggler::{Catalog, Status, Style, StyleFn};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>, status: iced::widget::toggler::Status) -> Style {
|
||||
class(self, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(_theme: &Theme, _status: Status) -> Style {
|
||||
Style {
|
||||
background: _theme.colors().background.darkest,
|
||||
background_border_width: 10.0,
|
||||
background_border_color: _theme.colors().background.darkest,
|
||||
foreground: _theme.colors().text.lightest,
|
||||
foreground_border_width: 10.0,
|
||||
foreground_border_color: _theme.colors().text.high_alpha,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue