#![allow(unused_variables, dead_code)]
use inflorescence_iced_widget::{nav_scrollable, report};
use libflorescence::prelude::*;
use iced::advanced::widget::text;
use iced::gradient::{ColorStop, Linear};
use iced::theme::{self, palette};
use iced::widget::{button, container, scrollable, text_editor, text_input};
use iced::{
border, color, window, Background, Border, Color, Gradient, Radians,
};
const BLUE: Color = color!(0x010032);
const BLUE_DARKER: Color = color!(0x010022);
const BLUE_LIGHT: Color = color!(0x020056);
const BLUE_DARK: Color = color!(0x000002);
const TEAL: Color = color!(0x3B8588);
const TEAL_LIGHTEST: Color = color!(0x97C9CB);
const TEAL_LIGHT: Color = color!(0x5F9FA2);
const TEAL_DARK: Color = color!(0x247074);
const TEAL_DARKEST: Color = color!(0x115B5E);
const MAGENTA: Color = color!(0xE933F6);
const MAGENTA_LIGHT: Color = color!(0xEE5DF8);
const MAGENTA_LIGHTER: Color = color!(0xF38EFA);
const MAGENTA_LIGHTEST: Color = color!(0xF7BCFC);
const MAGENTA_DARK: Color = color!(0xE208F1);
const MAGENTA_DARKER: Color = color!(0x9C06A6);
const MAGENTA_DARKEST: Color = color!(0x68436B);
const TURQOISE: Color = color!(0x75fad1);
const TURQOISE_LIGHT: Color = color!(0xA5FDE2);
const TURQOISE_DARK: Color = color!(0x4CF5C2);
const TURQOISE_DARKER: Color = color!(0x0CA592);
const TEXT_COLOR: Color = TEAL_LIGHTEST;
const TEXT_COLOR_FADED: Color = Color {
a: 0.7,
..TEXT_COLOR
};
const DELETED_BG_COLOR: Color = Color::from_rgba8(190, 37, 40, 0.25);
const ADDED_BG_COLOR: Color = Color::from_rgba8(47, 148, 11, 0.25);
const REPORT_INFO_COLOR: Color = color!(0x08992d);
const REPORT_WARNING_COLOR: Color = color!(0x999208);
const REPORT_ERROR_COLOR: Color = color!(0x9d140e);
const NAV_SECTION_BORDER_WIDTH: f32 = 2.0;
const APP_BG: Background = Background::Gradient(Gradient::Linear(Linear {
angle: Radians(0.),
stops: [
Some(ColorStop {
offset: 0.,
color: BLUE,
}),
Some(ColorStop {
offset: 1.,
color: BLUE_DARKER,
}),
None,
None,
None,
None,
None,
None,
],
}));
const ACTIONS_BG: Background = Background::Color(BLUE_DARKER);
const PALETTE: theme::Palette = theme::Palette {
background: BLUE,
text: scale_rgb(TEAL, 1.),
primary: TURQOISE,
success: TURQOISE,
warning: MAGENTA,
danger: MAGENTA,
};
#[derive(Debug, Default)]
pub struct Theme;
#[derive(Debug, Clone, Copy)]
pub enum Button {
Normal,
Selected,
ChildSelected,
}
#[derive(Debug, Clone, Copy)]
pub enum Container {
Normal,
FadedBorder,
AppBg,
ActionsBg,
ActionsHighlightBg,
DiffAddition,
DiffDeletion,
Report,
ReportLevel { level: report::Level, is_read: bool },
NavNonSelectedSection,
NavSelectedSection,
}
#[derive(Debug, Clone, Copy)]
pub enum Scrollable {
Normal,
Selected,
}
#[derive(Debug, Clone, Copy)]
pub enum Text {
Normal,
SlightlyFaded,
HighlightOnLightBg,
}
#[derive(Debug, Clone, Copy)]
pub enum TextEditor {
Normal,
}
#[derive(Debug, Clone, Copy)]
pub enum TextInput {
Normal,
}
pub fn theme<S>(_state: &S, _window_id: window::Id) -> Theme {
Theme
}
impl theme::Base for Theme {
fn base(&self) -> theme::Style {
theme::Style {
background_color: BLUE,
text_color: TEXT_COLOR,
}
}
fn palette(&self) -> Option<theme::Palette> {
None
}
fn default(_preference: theme::Mode) -> Self {
Theme
}
fn mode(&self) -> theme::Mode {
theme::Mode::None
}
fn name(&self) -> &str {
"inflorescence"
}
}
impl button::Catalog for Theme {
type Class<'a> = Button;
fn default<'a>() -> Self::Class<'a> {
Button::Normal
}
fn style(
&self,
class: &Self::Class<'_>,
status: button::Status,
) -> button::Style {
let base = button::Style {
background: Some(Background::Color(TURQOISE)),
text_color: TEAL_DARKEST,
..button::Style::default()
};
let base = match status {
button::Status::Active | button::Status::Pressed => base,
button::Status::Hovered => button::Style {
background: Some(Background::Color(TURQOISE_LIGHT)),
text_color: BLUE,
..base
},
button::Status::Disabled => button::Style {
background: base
.background
.map(|background| background.scale_alpha(0.7)),
text_color: base.text_color.scale_alpha(0.7),
..base
},
};
let border_radius = border::radius(2.);
const BORDER_WIDTH: f32 = 1.;
const CHILD_SELECTED_BLEND_RATIO: f32 = 0.5;
match class {
Button::Normal => button::Style {
border: Border {
color: Color::TRANSPARENT,
width: BORDER_WIDTH,
radius: border_radius,
},
..base
},
Button::Selected => button::Style {
background: Some(Background::Color(
if let button::Status::Hovered = status {
MAGENTA_LIGHTEST
} else {
MAGENTA_LIGHTER
},
)),
text_color: BLUE_DARK,
border: Border {
color: MAGENTA,
width: BORDER_WIDTH,
radius: border_radius,
},
..base
},
Button::ChildSelected => button::Style {
background: Some(Background::Color(
if let button::Status::Hovered = status {
blend(
MAGENTA_LIGHTEST,
TURQOISE_LIGHT,
CHILD_SELECTED_BLEND_RATIO,
)
} else {
blend(
MAGENTA_LIGHTER,
TURQOISE,
CHILD_SELECTED_BLEND_RATIO,
)
},
)),
text_color: BLUE_DARK,
border: Border {
color: MAGENTA,
width: BORDER_WIDTH,
radius: border_radius,
},
..base
},
}
}
}
impl container::Catalog for Theme {
type Class<'a> = Container;
fn default<'a>() -> Self::Class<'a> {
Container::Normal
}
fn style(&self, class: &Self::Class<'_>) -> container::Style {
match class {
Container::Normal => container::Style::default(),
Container::FadedBorder => container::Style {
border: Border {
color: TEXT_COLOR_FADED,
width: 2.0,
..default()
},
..default()
},
Container::AppBg => container::Style {
background: Some(APP_BG),
..default()
},
Container::ActionsBg => container::Style {
background: Some(ACTIONS_BG),
..default()
},
Container::ActionsHighlightBg => container::Style {
background: Some(Background::Color(MAGENTA_DARKEST)),
..default()
},
Container::DiffAddition => container::Style {
background: Some(Background::Color(ADDED_BG_COLOR)),
..default()
},
Container::DiffDeletion => container::Style {
background: Some(Background::Color(DELETED_BG_COLOR)),
..default()
},
Container::Report => container::Style {
background: Some(ACTIONS_BG),
border: Border {
color: TURQOISE_DARKER,
width: 1.0,
..default()
},
..default()
},
Container::ReportLevel { level, is_read } => {
let color = match level {
report::Level::Info => REPORT_INFO_COLOR,
report::Level::Warning => REPORT_WARNING_COLOR,
report::Level::Error => REPORT_ERROR_COLOR,
};
let color = if *is_read {
Color { a: 0.7, ..color }
} else {
color
};
container::Style {
background: Some(Background::Color(color)),
..default()
}
}
Container::NavNonSelectedSection => container::Style {
border: Border {
color: Color::TRANSPARENT,
width: NAV_SECTION_BORDER_WIDTH,
..default()
},
..default()
},
Container::NavSelectedSection => container::Style {
border: Border {
color: MAGENTA_LIGHT,
width: NAV_SECTION_BORDER_WIDTH,
..default()
},
..default()
},
}
}
}
impl scrollable::Catalog for Theme {
type Class<'a> = Scrollable;
fn default<'a>() -> Self::Class<'a> {
Scrollable::Normal
}
fn style(
&self,
class: &Self::Class<'_>,
status: scrollable::Status,
) -> scrollable::Style {
let background = palette::Background::new(
match class {
Scrollable::Normal => PALETTE.background,
Scrollable::Selected => MAGENTA_DARKEST,
},
PALETTE.text,
);
let scrollbar = scrollable::Rail {
background: Some(background.weak.color.into()),
border: border::rounded(2),
scroller: scrollable::Scroller {
background: Background::Color(match class {
Scrollable::Normal => background.strong.color,
Scrollable::Selected => MAGENTA_LIGHTER,
}),
border: border::rounded(2),
},
};
scrollable::Style {
container: container::Style::default(),
vertical_rail: scrollbar,
horizontal_rail: scrollbar,
gap: None,
auto_scroll: scrollable::AutoScroll {
background: Background::from(Color::default()),
border: default(),
shadow: default(),
icon: default(),
},
}
}
}
impl nav_scrollable::Catalog for Theme {
type Class<'a> = Scrollable;
fn default<'a>() -> Self::Class<'a> {
Scrollable::Normal
}
fn style(
&self,
class: &Self::Class<'_>,
status: nav_scrollable::Status,
) -> nav_scrollable::Style {
let background = palette::Background::new(
match class {
Scrollable::Normal => PALETTE.background,
Scrollable::Selected => MAGENTA_DARKEST,
},
PALETTE.text,
);
let scrollbar = nav_scrollable::Rail {
background: Some(background.weak.color.into()),
border: border::rounded(2),
scroller: nav_scrollable::Scroller {
background: Background::Color(match class {
Scrollable::Normal => background.strong.color,
Scrollable::Selected => MAGENTA_LIGHTER,
}),
border: border::rounded(2),
},
};
nav_scrollable::Style {
container: container::Style::default(),
vertical_rail: scrollbar,
horizontal_rail: scrollbar,
gap: None,
}
}
}
impl text::Catalog for Theme {
type Class<'a> = Text;
fn default<'a>() -> Self::Class<'a> {
Text::Normal
}
fn style(&self, item: &Self::Class<'_>) -> text::Style {
match item {
Text::Normal => text::Style { color: None },
Text::SlightlyFaded => text::Style {
color: Some(TEXT_COLOR_FADED),
},
Text::HighlightOnLightBg => text::Style {
color: Some(MAGENTA_DARKER),
},
}
}
}
impl text_editor::Catalog for Theme {
type Class<'a> = TextEditor;
fn default<'a>() -> Self::Class<'a> {
TextEditor::Normal
}
fn style(
&self,
class: &Self::Class<'_>,
status: text_editor::Status,
) -> text_editor::Style {
let background =
palette::Background::new(PALETTE.background, PALETTE.text);
let primary = palette::Primary::generate(
PALETTE.primary,
PALETTE.background,
PALETTE.text,
);
let active = text_editor::Style {
background: Background::Color(background.base.color),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: background.strong.color,
},
placeholder: background.strongest.color,
value: background.base.text,
selection: primary.weak.color,
};
match status {
text_editor::Status::Active => active,
text_editor::Status::Hovered => text_editor::Style {
border: Border {
color: background.base.text,
..active.border
},
..active
},
text_editor::Status::Focused { .. } => text_editor::Style {
border: Border {
color: primary.strong.color,
..active.border
},
..active
},
text_editor::Status::Disabled => text_editor::Style {
background: Background::Color(background.weak.color),
value: active.placeholder,
..active
},
}
}
}
impl text_input::Catalog for Theme {
type Class<'a> = TextInput;
fn default<'a>() -> Self::Class<'a> {
TextInput::Normal
}
fn style(
&self,
class: &Self::Class<'_>,
status: text_input::Status,
) -> text_input::Style {
let background =
palette::Background::new(PALETTE.background, PALETTE.text);
let primary = palette::Primary::generate(
PALETTE.primary,
PALETTE.background,
PALETTE.text,
);
let active = text_input::Style {
background: Background::Color(background.base.color),
border: Border {
radius: 2.0.into(),
width: 1.0,
color: background.strongest.color,
},
icon: background.weak.text,
placeholder: background.strongest.color,
value: background.base.text,
selection: primary.weak.color,
};
match status {
text_input::Status::Active => active,
text_input::Status::Hovered => text_input::Style {
border: Border {
color: background.base.text,
..active.border
},
..active
},
text_input::Status::Focused { .. } => text_input::Style {
border: Border {
color: primary.strong.color,
..active.border
},
..active
},
text_input::Status::Disabled => text_input::Style {
background: Background::Color(background.weak.color),
value: active.placeholder,
..active
},
}
}
}
const fn scale_rgb(color: Color, scale: f32) -> Color {
let Color { r, g, b, a } = color;
Color {
r: r * scale,
g: g * scale,
b: b * scale,
a,
}
}
const fn blend(a: Color, b: Color, a_ratio: f32) -> Color {
debug_assert!(a_ratio >= 0.0);
debug_assert!(a_ratio <= 1.0);
let b_ratio = 1.0 - a_ratio;
Color {
r: a.r * a_ratio + b.r * b_ratio,
g: a.g * a_ratio + b.g * b_ratio,
b: a.b * a_ratio + b.b * b_ratio,
a: a.a * a_ratio + b.a * b_ratio,
}
}