diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b36a714..22f3effb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +Added: + +- Ability to overwrite nickname colors by providing a hex string (see [buffer configuration](https://halloy.squidowl.org/configuration/buffer.html#buffernicknamecolor-section)). + # 2024.7 (2024-05-05) Added: diff --git a/book/src/configuration/buffer.md b/book/src/configuration/buffer.md index 61132adc..660bebc0 100644 --- a/book/src/configuration/buffer.md +++ b/book/src/configuration/buffer.md @@ -4,16 +4,31 @@ ## `[buffer.nickname]` Section +### `[buffer.nickname.color]` Section + ```toml -[buffer.nickname] -color = "unique" | "solid" -brackets = { left = "", right = "" } +[buffer.nickname.color] +kind = "unique" | "solid" +hex = "" +``` + +| Key | Description | Default | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `kind` | Controls whether nickname color is `"solid"` or `"unique"`. `"unique"` generates colors by randomzing a hue which is used together with the saturation and lightness from the action color provided by the theme. This color can be overwritten with `hex`. | `kind = "unique"` | +| `hex` | Overwrite the default color. Optional. | `not set` | + +### `[buffer.nickname.brackets]` Section + +```toml +[buffer.nickname.brackets] +left = "" +right = "" ``` -| Key | Description | Default | -| ---------- | ------------------------------------------------ | --------------------------- | -| `color` | Nickname colors. Can be `"unique"` or `"solid"`. | `"unique"` | -| `brackets` | Brackets for nicknames. | `{ left = "", right = "" }` | +| Key | Description | Default | +| ------- | ---------------------------- | ------------ | +| `left` | Left bracket for nicknames. | `left = ""` | +| `right` | Right bracket for nicknames. | `right = ""` | ## `[buffer.timestamp]` Section @@ -47,14 +62,25 @@ visibility = "always" | "focused" [buffer.channel.nicklist] enabled = true | false position = "left" | "right" -color = "unique" | "solid" ``` | Key | Description | Default | | ---------- | ------------------------------------------------ | ---------- | | `enabled` | Control if nicklist should be shown or not | `true` | | `position` | Nicklist position. Can be `"left"` or `"right"`. | `"right"` | -| `color` | Nickname colors. Can be `"unique"` or `"solid"`. | `"unique"` | + +### `[buffer.channel.nicklist.color]` Section + +```toml +[buffer.channel.nicklist.color] +kind = "unique" | "solid" +hex = "" +``` + +| Key | Description | Default | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `kind` | Controls whether nickname color is `"solid"` or `"unique"`. `"unique"` generates colors by randomzing a hue which is used together with the saturation and lightness from the action color provided by the theme. This color can be overwritten with `hex`. | `kind = "unique"` | +| `hex` | Overwrite the default color. Optional. | `not set` | ### `[buffer.channel.topic]` Section diff --git a/data/src/buffer.rs b/data/src/buffer.rs index 68f62d58..fb98e584 100644 --- a/data/src/buffer.rs +++ b/data/src/buffer.rs @@ -1,6 +1,6 @@ use core::fmt; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use crate::user::Nick; use crate::{channel, config, message, Server}; @@ -111,12 +111,45 @@ impl Brackets { #[derive(Debug, Clone, Copy, Default, Deserialize)] #[serde(rename_all = "kebab-case")] -pub enum Color { +pub enum ColorKind { Solid, #[default] Unique, } +#[derive(Debug, Clone, Default)] +pub struct Color { + pub kind: ColorKind, + pub hex: Option, +} + +// Support backwards compatibility of deserializing +// from a single "color kind" string +impl<'de> Deserialize<'de> for Color { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Data { + kind: ColorKind, + hex: Option, + } + + #[derive(Deserialize)] + #[serde(untagged)] + enum Format { + Kind(ColorKind), + Data(Data), + } + + Ok(match Format::deserialize(deserializer)? { + Format::Kind(kind) => Color { kind, hex: None }, + Format::Data(Data { kind, hex }) => Color { kind, hex }, + }) + } +} + #[derive(Debug, Clone, Copy)] pub enum Resize { None, diff --git a/data/src/config/buffer.rs b/data/src/config/buffer.rs index 51991cb1..1083fbe3 100644 --- a/data/src/config/buffer.rs +++ b/data/src/config/buffer.rs @@ -113,7 +113,7 @@ impl Default for Buffer { Buffer { timestamp: Timestamp::default(), nickname: Nickname { - color: Color::Unique, + color: Color::default(), brackets: Default::default(), }, text_input: Default::default(), diff --git a/data/src/config/channel.rs b/data/src/config/channel.rs index 6b6ba2c8..3acf1da5 100644 --- a/data/src/config/channel.rs +++ b/data/src/config/channel.rs @@ -11,7 +11,7 @@ pub struct Channel { pub topic: Topic, } -#[derive(Debug, Clone, Copy, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct Nicklist { #[serde(default = "default_bool_true")] pub enabled: bool, diff --git a/data/src/theme.rs b/data/src/theme.rs index 204558ba..869f9a72 100644 --- a/data/src/theme.rs +++ b/data/src/theme.rs @@ -137,7 +137,7 @@ impl Default for Palette { } } -fn hex_to_color(hex: &str) -> Option { +pub fn hex_to_color(hex: &str) -> Option { if hex.len() == 7 { let hash = &hex[0..1]; let r = u8::from_str_radix(&hex[1..3], 16); diff --git a/data/src/user.rs b/data/src/user.rs index f52586f7..a2a7e7ea 100644 --- a/data/src/user.rs +++ b/data/src/user.rs @@ -5,7 +5,12 @@ use std::hash::Hash; use irc::proto; use serde::{Deserialize, Serialize}; -use crate::{buffer, config::buffer::UsernameFormat, mode}; +use crate::{ + buffer, + config::buffer::UsernameFormat, + mode, + theme::{hex_to_color, Colors}, +}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(into = "String")] @@ -120,10 +125,22 @@ impl From for User { } impl User { - pub fn color_seed(&self, color: &buffer::Color) -> Option { - match color { - buffer::Color::Solid => None, - buffer::Color::Unique => Some(self.nickname().as_ref().to_string()), + pub fn nick_color(&self, colors: &Colors, color: &buffer::Color) -> NickColor { + let buffer::Color { kind, hex } = color; + let color = hex + .as_deref() + .and_then(hex_to_color) + .unwrap_or(colors.action.base); + + match kind { + buffer::ColorKind::Solid => NickColor { + seed: None, + color, + }, + buffer::ColorKind::Unique => NickColor { + seed: Some(self.nickname().as_ref().to_string()), + color, + }, } } @@ -211,6 +228,12 @@ impl fmt::Display for User { } } +#[derive(Debug, Clone)] +pub struct NickColor { + pub seed: Option, + pub color: iced_core::Color, +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Nick(String); diff --git a/src/buffer/channel.rs b/src/buffer/channel.rs index 58478c28..13060ef9 100644 --- a/src/buffer/channel.rs +++ b/src/buffer/channel.rs @@ -63,7 +63,7 @@ pub fn view<'a>( |theme| { theme::selectable_text::nickname( theme, - user.color_seed(&config.buffer.nickname.color), + user.nick_color(theme.colors(), &config.buffer.nickname.color), user.is_away(), ) }, @@ -304,7 +304,7 @@ mod nick_list { let content = text(user.to_string()).style(|theme| { theme::text::nickname( theme, - user.color_seed(&config.buffer.channel.nicklist.color), + user.nick_color(theme.colors(), &config.buffer.channel.nicklist.color), user.is_away(), ) }); diff --git a/src/buffer/channel/topic.rs b/src/buffer/channel/topic.rs index 5524711a..38f15567 100644 --- a/src/buffer/channel/topic.rs +++ b/src/buffer/channel/topic.rs @@ -26,7 +26,7 @@ pub fn view<'a>( selectable_text(who).style(|theme| { theme::selectable_text::nickname( theme, - user.color_seed(&config.buffer.nickname.color), + user.nick_color(theme.colors(), &config.buffer.nickname.color), false, ) }), diff --git a/src/buffer/query.rs b/src/buffer/query.rs index e6a97d61..ce2db797 100644 --- a/src/buffer/query.rs +++ b/src/buffer/query.rs @@ -51,7 +51,7 @@ pub fn view<'a>( |theme| { theme::selectable_text::nickname( theme, - user.color_seed(&config.buffer.nickname.color), + user.nick_color(theme.colors(), &config.buffer.nickname.color), false, ) }, diff --git a/src/theme.rs b/src/theme.rs index 9a1f94ee..e6676dbd 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -46,7 +46,7 @@ impl Theme { } } - fn colors(&self) -> &Colors { + pub fn colors(&self) -> &Colors { match self { Theme::Selected(selected) => &selected.colors, Theme::Preview { preview, .. } => &preview.colors, diff --git a/src/theme/selectable_text.rs b/src/theme/selectable_text.rs index 817ec10f..91cc83e6 100644 --- a/src/theme/selectable_text.rs +++ b/src/theme/selectable_text.rs @@ -1,4 +1,4 @@ -use data::message; +use data::{message, user::NickColor}; use crate::widget::selectable_text::{Catalog, Style, StyleFn}; @@ -46,8 +46,8 @@ pub fn accent(theme: &Theme) -> Style { } } -pub fn nickname(theme: &Theme, seed: Option, transparent: bool) -> Style { - let color = text::nickname(theme, seed, transparent).color; +pub fn nickname(theme: &Theme, nick_color: NickColor, transparent: bool) -> Style { + let color = text::nickname(theme, nick_color, transparent).color; Style { color, diff --git a/src/theme/text.rs b/src/theme/text.rs index 998fb3a0..6feee534 100644 --- a/src/theme/text.rs +++ b/src/theme/text.rs @@ -1,4 +1,4 @@ -use data::theme::{alpha, randomize_color}; +use data::{theme::{alpha, randomize_color}, user::NickColor}; use iced::widget::text::{Catalog, Style, StyleFn}; use super::Theme; @@ -67,29 +67,26 @@ pub fn transparent_accent(theme: &Theme) -> Style { } } -pub fn nickname(theme: &Theme, seed: Option, transparent: bool) -> Style { +pub fn nickname(theme: &Theme, nick_color: NickColor, transparent: bool) -> Style { let dark_theme = theme.colors().is_dark_theme(); + let NickColor { color, seed } = nick_color; - if seed.is_none() { - let color = match transparent { - true => theme.colors().text.med_alpha, - false => theme.colors().text.base, + let Some(seed) = seed else { + let color = if transparent { + alpha(color, if dark_theme { 0.2 } else { 0.4 }) + } else { + color }; return Style { color: Some(color) }; - } - - let original_color = theme.colors().action.base; - let randomized_color = seed - .as_deref() - .map(|seed| randomize_color(original_color, seed)) - .unwrap_or_else(|| original_color); + }; + let randomized_color = randomize_color(color, &seed); let color = if transparent { alpha(randomized_color, if dark_theme { 0.2 } else { 0.4 }) } else { randomized_color }; - Style { color: Some(color) } +Style { color: Some(color) } }