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), }, 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), muted_bg: rotate_color(theme.list.muted_bg, degrees), muted_fg: rotate_color(theme.list.muted_fg, degrees), soloed_bg: rotate_color(theme.list.soloed_bg, degrees), soloed_fg: rotate_color(theme.list.soloed_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), }, } }