Euclidean + hue rotation

This commit is contained in:
2026-02-02 13:25:27 +01:00
parent 7348bd38b1
commit d54d9218c1
21 changed files with 1338 additions and 53 deletions

View File

@@ -0,0 +1,345 @@
use ratatui::style::Color;
use super::*;
fn rgb_to_hsv(r: u8, g: u8, b: u8) -> (f32, f32, f32) {
let r = r as f32 / 255.0;
let g = g as f32 / 255.0;
let b = b as f32 / 255.0;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let h = if delta == 0.0 {
0.0
} else if max == r {
60.0 * (((g - b) / delta) % 6.0)
} else if max == g {
60.0 * (((b - r) / delta) + 2.0)
} else {
60.0 * (((r - g) / delta) + 4.0)
};
let h = if h < 0.0 { h + 360.0 } else { h };
let s = if max == 0.0 { 0.0 } else { delta / max };
let v = max;
(h, s, v)
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
let c = v * s;
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
let m = v - c;
let (r, g, b) = if h < 60.0 {
(c, x, 0.0)
} else if h < 120.0 {
(x, c, 0.0)
} else if h < 180.0 {
(0.0, c, x)
} else if h < 240.0 {
(0.0, x, c)
} else if h < 300.0 {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
(
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
)
}
fn rotate_hue_rgb(r: u8, g: u8, b: u8, degrees: f32) -> (u8, u8, u8) {
let (h, s, v) = rgb_to_hsv(r, g, b);
let new_h = (h + degrees) % 360.0;
let new_h = if new_h < 0.0 { new_h + 360.0 } else { new_h };
hsv_to_rgb(new_h, s, v)
}
fn rotate_color(color: Color, degrees: f32) -> Color {
match color {
Color::Rgb(r, g, b) => {
let (nr, ng, nb) = rotate_hue_rgb(r, g, b, degrees);
Color::Rgb(nr, ng, nb)
}
_ => color,
}
}
fn rotate_tuple(tuple: (u8, u8, u8), degrees: f32) -> (u8, u8, u8) {
rotate_hue_rgb(tuple.0, tuple.1, tuple.2, degrees)
}
fn rotate_color_pair(pair: (Color, Color), degrees: f32) -> (Color, Color) {
(rotate_color(pair.0, degrees), rotate_color(pair.1, degrees))
}
pub fn rotate_theme(theme: ThemeColors, degrees: f32) -> ThemeColors {
if degrees == 0.0 {
return theme;
}
ThemeColors {
ui: UiColors {
bg: rotate_color(theme.ui.bg, degrees),
bg_rgb: rotate_tuple(theme.ui.bg_rgb, degrees),
text_primary: rotate_color(theme.ui.text_primary, degrees),
text_muted: rotate_color(theme.ui.text_muted, degrees),
text_dim: rotate_color(theme.ui.text_dim, degrees),
border: rotate_color(theme.ui.border, degrees),
header: rotate_color(theme.ui.header, degrees),
unfocused: rotate_color(theme.ui.unfocused, degrees),
accent: rotate_color(theme.ui.accent, degrees),
surface: rotate_color(theme.ui.surface, degrees),
},
status: StatusColors {
playing_bg: rotate_color(theme.status.playing_bg, degrees),
playing_fg: rotate_color(theme.status.playing_fg, degrees),
stopped_bg: rotate_color(theme.status.stopped_bg, degrees),
stopped_fg: rotate_color(theme.status.stopped_fg, degrees),
fill_on: rotate_color(theme.status.fill_on, degrees),
fill_off: rotate_color(theme.status.fill_off, degrees),
fill_bg: rotate_color(theme.status.fill_bg, degrees),
},
selection: SelectionColors {
cursor_bg: rotate_color(theme.selection.cursor_bg, degrees),
cursor_fg: rotate_color(theme.selection.cursor_fg, degrees),
selected_bg: rotate_color(theme.selection.selected_bg, degrees),
selected_fg: rotate_color(theme.selection.selected_fg, degrees),
in_range_bg: rotate_color(theme.selection.in_range_bg, degrees),
in_range_fg: rotate_color(theme.selection.in_range_fg, degrees),
cursor: rotate_color(theme.selection.cursor, degrees),
selected: rotate_color(theme.selection.selected, degrees),
in_range: rotate_color(theme.selection.in_range, degrees),
},
tile: TileColors {
playing_active_bg: rotate_color(theme.tile.playing_active_bg, degrees),
playing_active_fg: rotate_color(theme.tile.playing_active_fg, degrees),
playing_inactive_bg: rotate_color(theme.tile.playing_inactive_bg, degrees),
playing_inactive_fg: rotate_color(theme.tile.playing_inactive_fg, degrees),
active_bg: rotate_color(theme.tile.active_bg, degrees),
active_fg: rotate_color(theme.tile.active_fg, degrees),
inactive_bg: rotate_color(theme.tile.inactive_bg, degrees),
inactive_fg: rotate_color(theme.tile.inactive_fg, degrees),
active_selected_bg: rotate_color(theme.tile.active_selected_bg, degrees),
active_in_range_bg: rotate_color(theme.tile.active_in_range_bg, degrees),
link_bright: [
rotate_tuple(theme.tile.link_bright[0], degrees),
rotate_tuple(theme.tile.link_bright[1], degrees),
rotate_tuple(theme.tile.link_bright[2], degrees),
rotate_tuple(theme.tile.link_bright[3], degrees),
rotate_tuple(theme.tile.link_bright[4], degrees),
],
link_dim: [
rotate_tuple(theme.tile.link_dim[0], degrees),
rotate_tuple(theme.tile.link_dim[1], degrees),
rotate_tuple(theme.tile.link_dim[2], degrees),
rotate_tuple(theme.tile.link_dim[3], degrees),
rotate_tuple(theme.tile.link_dim[4], degrees),
],
},
header: HeaderColors {
tempo_bg: rotate_color(theme.header.tempo_bg, degrees),
tempo_fg: rotate_color(theme.header.tempo_fg, degrees),
bank_bg: rotate_color(theme.header.bank_bg, degrees),
bank_fg: rotate_color(theme.header.bank_fg, degrees),
pattern_bg: rotate_color(theme.header.pattern_bg, degrees),
pattern_fg: rotate_color(theme.header.pattern_fg, degrees),
stats_bg: rotate_color(theme.header.stats_bg, degrees),
stats_fg: rotate_color(theme.header.stats_fg, degrees),
},
modal: ModalColors {
border: rotate_color(theme.modal.border, degrees),
border_accent: rotate_color(theme.modal.border_accent, degrees),
border_warn: rotate_color(theme.modal.border_warn, degrees),
border_dim: rotate_color(theme.modal.border_dim, degrees),
confirm: rotate_color(theme.modal.confirm, degrees),
rename: rotate_color(theme.modal.rename, degrees),
input: rotate_color(theme.modal.input, degrees),
editor: rotate_color(theme.modal.editor, degrees),
preview: rotate_color(theme.modal.preview, degrees),
},
flash: FlashColors {
error_bg: rotate_color(theme.flash.error_bg, degrees),
error_fg: rotate_color(theme.flash.error_fg, degrees),
success_bg: rotate_color(theme.flash.success_bg, degrees),
success_fg: rotate_color(theme.flash.success_fg, degrees),
info_bg: rotate_color(theme.flash.info_bg, degrees),
info_fg: rotate_color(theme.flash.info_fg, degrees),
event_rgb: rotate_tuple(theme.flash.event_rgb, degrees),
},
list: ListColors {
playing_bg: rotate_color(theme.list.playing_bg, degrees),
playing_fg: rotate_color(theme.list.playing_fg, degrees),
staged_play_bg: rotate_color(theme.list.staged_play_bg, degrees),
staged_play_fg: rotate_color(theme.list.staged_play_fg, degrees),
staged_stop_bg: rotate_color(theme.list.staged_stop_bg, degrees),
staged_stop_fg: rotate_color(theme.list.staged_stop_fg, degrees),
edit_bg: rotate_color(theme.list.edit_bg, degrees),
edit_fg: rotate_color(theme.list.edit_fg, degrees),
hover_bg: rotate_color(theme.list.hover_bg, degrees),
hover_fg: rotate_color(theme.list.hover_fg, degrees),
},
link_status: LinkStatusColors {
disabled: rotate_color(theme.link_status.disabled, degrees),
connected: rotate_color(theme.link_status.connected, degrees),
listening: rotate_color(theme.link_status.listening, degrees),
},
syntax: SyntaxColors {
gap_bg: rotate_color(theme.syntax.gap_bg, degrees),
executed_bg: rotate_color(theme.syntax.executed_bg, degrees),
selected_bg: rotate_color(theme.syntax.selected_bg, degrees),
emit: rotate_color_pair(theme.syntax.emit, degrees),
number: rotate_color_pair(theme.syntax.number, degrees),
string: rotate_color_pair(theme.syntax.string, degrees),
comment: rotate_color_pair(theme.syntax.comment, degrees),
keyword: rotate_color_pair(theme.syntax.keyword, degrees),
stack_op: rotate_color_pair(theme.syntax.stack_op, degrees),
operator: rotate_color_pair(theme.syntax.operator, degrees),
sound: rotate_color_pair(theme.syntax.sound, degrees),
param: rotate_color_pair(theme.syntax.param, degrees),
context: rotate_color_pair(theme.syntax.context, degrees),
note: rotate_color_pair(theme.syntax.note, degrees),
interval: rotate_color_pair(theme.syntax.interval, degrees),
variable: rotate_color_pair(theme.syntax.variable, degrees),
vary: rotate_color_pair(theme.syntax.vary, degrees),
generator: rotate_color_pair(theme.syntax.generator, degrees),
default: rotate_color_pair(theme.syntax.default, degrees),
},
table: TableColors {
row_even: rotate_color(theme.table.row_even, degrees),
row_odd: rotate_color(theme.table.row_odd, degrees),
},
values: ValuesColors {
tempo: rotate_color(theme.values.tempo, degrees),
value: rotate_color(theme.values.value, degrees),
},
hint: HintColors {
key: rotate_color(theme.hint.key, degrees),
text: rotate_color(theme.hint.text, degrees),
},
view_badge: ViewBadgeColors {
bg: rotate_color(theme.view_badge.bg, degrees),
fg: rotate_color(theme.view_badge.fg, degrees),
},
nav: NavColors {
selected_bg: rotate_color(theme.nav.selected_bg, degrees),
selected_fg: rotate_color(theme.nav.selected_fg, degrees),
unselected_bg: rotate_color(theme.nav.unselected_bg, degrees),
unselected_fg: rotate_color(theme.nav.unselected_fg, degrees),
},
editor_widget: EditorWidgetColors {
cursor_bg: rotate_color(theme.editor_widget.cursor_bg, degrees),
cursor_fg: rotate_color(theme.editor_widget.cursor_fg, degrees),
selection_bg: rotate_color(theme.editor_widget.selection_bg, degrees),
completion_bg: rotate_color(theme.editor_widget.completion_bg, degrees),
completion_fg: rotate_color(theme.editor_widget.completion_fg, degrees),
completion_selected: rotate_color(theme.editor_widget.completion_selected, degrees),
completion_example: rotate_color(theme.editor_widget.completion_example, degrees),
},
browser: BrowserColors {
directory: rotate_color(theme.browser.directory, degrees),
project_file: rotate_color(theme.browser.project_file, degrees),
selected: rotate_color(theme.browser.selected, degrees),
file: rotate_color(theme.browser.file, degrees),
focused_border: rotate_color(theme.browser.focused_border, degrees),
unfocused_border: rotate_color(theme.browser.unfocused_border, degrees),
root: rotate_color(theme.browser.root, degrees),
file_icon: rotate_color(theme.browser.file_icon, degrees),
folder_icon: rotate_color(theme.browser.folder_icon, degrees),
empty_text: rotate_color(theme.browser.empty_text, degrees),
},
input: InputColors {
text: rotate_color(theme.input.text, degrees),
cursor: rotate_color(theme.input.cursor, degrees),
hint: rotate_color(theme.input.hint, degrees),
},
search: SearchColors {
active: rotate_color(theme.search.active, degrees),
inactive: rotate_color(theme.search.inactive, degrees),
match_bg: rotate_color(theme.search.match_bg, degrees),
match_fg: rotate_color(theme.search.match_fg, degrees),
},
markdown: MarkdownColors {
h1: rotate_color(theme.markdown.h1, degrees),
h2: rotate_color(theme.markdown.h2, degrees),
h3: rotate_color(theme.markdown.h3, degrees),
code: rotate_color(theme.markdown.code, degrees),
code_border: rotate_color(theme.markdown.code_border, degrees),
link: rotate_color(theme.markdown.link, degrees),
link_url: rotate_color(theme.markdown.link_url, degrees),
quote: rotate_color(theme.markdown.quote, degrees),
text: rotate_color(theme.markdown.text, degrees),
list: rotate_color(theme.markdown.list, degrees),
},
engine: EngineColors {
header: rotate_color(theme.engine.header, degrees),
header_focused: rotate_color(theme.engine.header_focused, degrees),
divider: rotate_color(theme.engine.divider, degrees),
scroll_indicator: rotate_color(theme.engine.scroll_indicator, degrees),
label: rotate_color(theme.engine.label, degrees),
label_focused: rotate_color(theme.engine.label_focused, degrees),
label_dim: rotate_color(theme.engine.label_dim, degrees),
value: rotate_color(theme.engine.value, degrees),
focused: rotate_color(theme.engine.focused, degrees),
normal: rotate_color(theme.engine.normal, degrees),
dim: rotate_color(theme.engine.dim, degrees),
path: rotate_color(theme.engine.path, degrees),
border_magenta: rotate_color(theme.engine.border_magenta, degrees),
border_green: rotate_color(theme.engine.border_green, degrees),
border_cyan: rotate_color(theme.engine.border_cyan, degrees),
separator: rotate_color(theme.engine.separator, degrees),
hint_active: rotate_color(theme.engine.hint_active, degrees),
hint_inactive: rotate_color(theme.engine.hint_inactive, degrees),
},
dict: DictColors {
word_name: rotate_color(theme.dict.word_name, degrees),
word_bg: rotate_color(theme.dict.word_bg, degrees),
alias: rotate_color(theme.dict.alias, degrees),
stack_sig: rotate_color(theme.dict.stack_sig, degrees),
description: rotate_color(theme.dict.description, degrees),
example: rotate_color(theme.dict.example, degrees),
category_focused: rotate_color(theme.dict.category_focused, degrees),
category_selected: rotate_color(theme.dict.category_selected, degrees),
category_normal: rotate_color(theme.dict.category_normal, degrees),
category_dimmed: rotate_color(theme.dict.category_dimmed, degrees),
border_focused: rotate_color(theme.dict.border_focused, degrees),
border_normal: rotate_color(theme.dict.border_normal, degrees),
header_desc: rotate_color(theme.dict.header_desc, degrees),
},
title: TitleColors {
big_title: rotate_color(theme.title.big_title, degrees),
author: rotate_color(theme.title.author, degrees),
link: rotate_color(theme.title.link, degrees),
license: rotate_color(theme.title.license, degrees),
prompt: rotate_color(theme.title.prompt, degrees),
subtitle: rotate_color(theme.title.subtitle, degrees),
},
meter: MeterColors {
low: rotate_color(theme.meter.low, degrees),
mid: rotate_color(theme.meter.mid, degrees),
high: rotate_color(theme.meter.high, degrees),
low_rgb: rotate_tuple(theme.meter.low_rgb, degrees),
mid_rgb: rotate_tuple(theme.meter.mid_rgb, degrees),
high_rgb: rotate_tuple(theme.meter.high_rgb, degrees),
},
sparkle: SparkleColors {
colors: [
rotate_tuple(theme.sparkle.colors[0], degrees),
rotate_tuple(theme.sparkle.colors[1], degrees),
rotate_tuple(theme.sparkle.colors[2], degrees),
rotate_tuple(theme.sparkle.colors[3], degrees),
rotate_tuple(theme.sparkle.colors[4], degrees),
],
},
confirm: ConfirmColors {
border: rotate_color(theme.confirm.border, degrees),
button_selected_bg: rotate_color(theme.confirm.button_selected_bg, degrees),
button_selected_fg: rotate_color(theme.confirm.button_selected_fg, degrees),
},
}
}