use kas::draw::color::{Rgba, Rgba8Srgb};
use kas::event::EventState;
use kas::theme::Background;
use kas::WidgetId;
use std::str::FromStr;
const MULT_DEPRESS: f32 = 0.75;
const MULT_HIGHLIGHT: f32 = 1.25;
const MIN_HIGHLIGHT: f32 = 0.2;
bitflags::bitflags! {
#[derive(Default)]
pub struct InputState: u8 {
const DISABLED = 1 << 0;
const HOVER = 1 << 2;
const DEPRESS = 1 << 3;
const NAV_FOCUS = 1 << 4;
const CHAR_FOCUS = 1 << 5;
const SEL_FOCUS = 1 << 6;
}
}
impl InputState {
pub fn new_all(ev: &EventState, id: &WidgetId) -> Self {
let mut state = Self::new_except_depress(ev, id);
if ev.is_depressed(id) {
state |= InputState::DEPRESS;
}
state
}
pub fn new_except_depress(ev: &EventState, id: &WidgetId) -> Self {
let (char_focus, sel_focus) = ev.has_char_focus(id);
let mut state = InputState::empty();
if ev.is_disabled(id) {
state |= InputState::DISABLED;
}
if ev.is_hovered(id) {
state |= InputState::HOVER;
}
if ev.has_nav_focus(id) {
state |= InputState::NAV_FOCUS;
}
if char_focus {
state |= InputState::CHAR_FOCUS;
}
if sel_focus {
state |= InputState::SEL_FOCUS;
}
state
}
pub fn new2(ev: &EventState, id: &WidgetId, id2: &WidgetId) -> Self {
let mut state = Self::new_all(ev, id);
if ev.is_hovered(id2) {
state |= InputState::HOVER;
}
state
}
#[inline]
pub fn disabled(self) -> bool {
self.contains(InputState::DISABLED)
}
#[inline]
pub fn hover(self) -> bool {
self.contains(InputState::HOVER)
}
#[inline]
pub fn depress(self) -> bool {
self.contains(InputState::DEPRESS)
}
#[inline]
pub fn nav_focus(self) -> bool {
self.contains(InputState::NAV_FOCUS)
}
#[inline]
pub fn char_focus(self) -> bool {
self.contains(InputState::CHAR_FOCUS)
}
#[inline]
pub fn sel_focus(self) -> bool {
self.contains(InputState::SEL_FOCUS)
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "config", derive(serde::Serialize, serde::Deserialize))]
pub struct Colors<C> {
pub is_dark: bool,
pub background: C,
pub frame: C,
pub edit_bg: C,
pub edit_bg_disabled: C,
pub edit_bg_error: C,
pub accent: C,
pub accent_soft: C,
pub nav_focus: C,
pub text: C,
pub text_invert: C,
pub text_disabled: C,
pub text_sel_bg: C,
}
pub type ColorsSrgb = Colors<Rgba8Srgb>;
pub type ColorsLinear = Colors<Rgba>;
impl From<ColorsSrgb> for ColorsLinear {
fn from(col: ColorsSrgb) -> Self {
Colors {
is_dark: col.is_dark,
background: col.background.into(),
frame: col.frame.into(),
accent: col.accent.into(),
accent_soft: col.accent_soft.into(),
nav_focus: col.nav_focus.into(),
edit_bg: col.edit_bg.into(),
edit_bg_disabled: col.edit_bg_disabled.into(),
edit_bg_error: col.edit_bg_error.into(),
text: col.text.into(),
text_invert: col.text_invert.into(),
text_disabled: col.text_disabled.into(),
text_sel_bg: col.text_sel_bg.into(),
}
}
}
impl From<ColorsLinear> for ColorsSrgb {
fn from(col: ColorsLinear) -> Self {
Colors {
is_dark: col.is_dark,
background: col.background.into(),
frame: col.frame.into(),
accent: col.accent.into(),
accent_soft: col.accent_soft.into(),
nav_focus: col.nav_focus.into(),
edit_bg: col.edit_bg.into(),
edit_bg_disabled: col.edit_bg_disabled.into(),
edit_bg_error: col.edit_bg_error.into(),
text: col.text.into(),
text_invert: col.text_invert.into(),
text_disabled: col.text_disabled.into(),
text_sel_bg: col.text_sel_bg.into(),
}
}
}
impl Default for ColorsLinear {
#[cfg(feature = "dark-light")]
fn default() -> Self {
match dark_light::detect() {
dark_light::Mode::Dark => ColorsSrgb::dark().into(),
dark_light::Mode::Light => ColorsSrgb::light().into(),
}
}
#[cfg(not(feature = "dark-light"))]
#[inline]
fn default() -> Self {
ColorsSrgb::default().into()
}
}
impl Default for ColorsSrgb {
#[inline]
fn default() -> Self {
ColorsSrgb::light()
}
}
impl ColorsSrgb {
pub fn light() -> Self {
Colors {
is_dark: false,
background: Rgba8Srgb::from_str("#FAFAFA").unwrap(),
frame: Rgba8Srgb::from_str("#BCBCBC").unwrap(),
accent: Rgba8Srgb::from_str("#8347f2").unwrap(),
accent_soft: Rgba8Srgb::from_str("#B38DF9").unwrap(),
nav_focus: Rgba8Srgb::from_str("#7E3FF2").unwrap(),
edit_bg: Rgba8Srgb::from_str("#FAFAFA").unwrap(),
edit_bg_disabled: Rgba8Srgb::from_str("#DCDCDC").unwrap(),
edit_bg_error: Rgba8Srgb::from_str("#FFBCBC").unwrap(),
text: Rgba8Srgb::from_str("#000000").unwrap(),
text_invert: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
text_disabled: Rgba8Srgb::from_str("#AAAAAA").unwrap(),
text_sel_bg: Rgba8Srgb::from_str("#A172FA").unwrap(),
}
}
pub fn dark() -> Self {
Colors {
is_dark: true,
background: Rgba8Srgb::from_str("#404040").unwrap(),
frame: Rgba8Srgb::from_str("#AAAAAA").unwrap(),
accent: Rgba8Srgb::from_str("#F74C00").unwrap(),
accent_soft: Rgba8Srgb::from_str("#E77346").unwrap(),
nav_focus: Rgba8Srgb::from_str("#D03E00").unwrap(),
edit_bg: Rgba8Srgb::from_str("#303030").unwrap(),
edit_bg_disabled: Rgba8Srgb::from_str("#606060").unwrap(),
edit_bg_error: Rgba8Srgb::from_str("#a06868").unwrap(),
text: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
text_invert: Rgba8Srgb::from_str("#000000").unwrap(),
text_disabled: Rgba8Srgb::from_str("#CBCBCB").unwrap(),
text_sel_bg: Rgba8Srgb::from_str("#E77346").unwrap(),
}
}
pub fn blue() -> Self {
Colors {
is_dark: false,
background: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
frame: Rgba8Srgb::from_str("#DADADA").unwrap(),
accent: Rgba8Srgb::from_str("#3fafd7").unwrap(),
accent_soft: Rgba8Srgb::from_str("#7CDAFF").unwrap(),
nav_focus: Rgba8Srgb::from_str("#3B697A").unwrap(),
edit_bg: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
edit_bg_disabled: Rgba8Srgb::from_str("#DCDCDC").unwrap(),
edit_bg_error: Rgba8Srgb::from_str("#FFBCBC").unwrap(),
text: Rgba8Srgb::from_str("#000000").unwrap(),
text_invert: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
text_disabled: Rgba8Srgb::from_str("#AAAAAA").unwrap(),
text_sel_bg: Rgba8Srgb::from_str("#6CC0E1").unwrap(),
}
}
}
impl ColorsLinear {
pub fn adjust_for_state(col: Rgba, state: InputState) -> Rgba {
if state.disabled() {
col.average()
} else if state.depress() {
col.multiply(MULT_DEPRESS)
} else if state.hover() || state.char_focus() {
col.multiply(MULT_HIGHLIGHT).max(MIN_HIGHLIGHT)
} else {
col
}
}
pub fn from_bg(&self, bg: Background, state: InputState, force_accent: bool) -> Rgba {
let use_accent = force_accent || state.depress() || state.nav_focus();
let col = match bg {
_ if state.disabled() => self.edit_bg_disabled,
Background::Default if use_accent => self.accent_soft,
Background::Default => self.background,
Background::Error => self.edit_bg_error,
Background::Rgb(rgb) => rgb.into(),
};
Self::adjust_for_state(col, state)
}
pub fn from_edit_bg(&self, bg: Background, state: InputState) -> Rgba {
let mut col = match bg {
_ if state.disabled() => self.edit_bg_disabled,
Background::Default => self.edit_bg,
Background::Error => self.edit_bg_error,
Background::Rgb(rgb) => rgb.into(),
};
if state.depress() {
col = col.multiply(MULT_DEPRESS);
}
col
}
pub fn nav_region(&self, state: InputState) -> Option<Rgba> {
if state.nav_focus() && !state.disabled() {
Some(self.nav_focus)
} else {
None
}
}
#[inline]
pub fn accent_state(&self, state: InputState) -> Rgba {
Self::adjust_for_state(self.accent, state)
}
#[inline]
pub fn accent_soft_state(&self, state: InputState) -> Rgba {
Self::adjust_for_state(self.accent_soft, state)
}
#[inline]
pub fn check_mark_state(&self, state: InputState) -> Rgba {
Self::adjust_for_state(self.accent, state)
}
pub fn menu_entry(&self, state: InputState) -> Option<Rgba> {
if state.depress() || state.nav_focus() {
Some(self.accent_soft.multiply(MULT_DEPRESS))
} else {
None
}
}
pub fn text_over(&self, bg: Rgba) -> Rgba {
let bg_sum = bg.sum();
if (bg_sum - self.text_invert.sum()).abs() > (bg_sum - self.text.sum()).abs() {
self.text_invert
} else {
self.text
}
}
}