maolan 0.2.3

Rust DAW application for recording, editing, routing, automation, export, and plugin hosting
use iced::{Background, Border, Color, gradient, widget::container};

fn rgb(r: u8, g: u8, b: u8) -> Color {
    Color::from_rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)
}

pub fn brighten(color: Color, amount: f32) -> Color {
    Color {
        r: (color.r + amount).min(1.0),
        g: (color.g + amount).min(1.0),
        b: (color.b + amount).min(1.0),
        a: color.a,
    }
}

pub fn darken(color: Color, amount: f32) -> Color {
    Color {
        r: (color.r - amount).max(0.0),
        g: (color.g - amount).max(0.0),
        b: (color.b - amount).max(0.0),
        a: color.a,
    }
}

pub const STRIP_CORNER_RADIUS: f32 = 7.0;

pub fn strip(selected: bool, color: Option<iced::Color>) -> container::Style {
    let border_color = if selected {
        rgb(114, 170, 240)
    } else {
        rgb(39, 47, 63)
    };
    let base = color.unwrap_or_else(|| rgb(26, 32, 46));
    let side = darken(base, 0.040);
    let center = brighten(base, 0.045);

    container::Style {
        background: Some(Background::Gradient(
            gradient::Linear::new(90.0)
                .add_stop(0.0, side)
                .add_stop(0.5, center)
                .add_stop(1.0, side)
                .into(),
        )),
        border: Border {
            color: border_color,
            width: if selected { 1.5 } else { 1.0 },
            radius: STRIP_CORNER_RADIUS.into(),
        },
        ..container::Style::default()
    }
}

pub fn slot() -> container::Style {
    container::Style {
        border: Border {
            color: rgb(39, 47, 63),
            width: 1.0,
            radius: 4.0.into(),
        },
        ..container::Style::default()
    }
}

/// Master-track slot for the selected scene: border-only highlight in the
/// selection color. Applies both while stopped (the scene Play will start)
/// and while playing (the scene that launches next).
pub fn slot_queued() -> container::Style {
    container::Style {
        border: Border {
            color: rgb(114, 170, 240),
            width: 1.5,
            radius: 4.0.into(),
        },
        ..container::Style::default()
    }
}

/// Master-track slot whose scene is currently playing.
pub fn slot_playing() -> container::Style {
    container::Style {
        background: Some(Background::Color(rgb(38, 78, 52))),
        border: Border {
            color: rgb(96, 200, 130),
            width: 1.0,
            radius: 4.0.into(),
        },
        ..container::Style::default()
    }
}

/// Top/bottom edge highlight for strips whose immediate parent folder is
/// selected. Matches the selection border color.
pub fn strip_parent_edge_highlight() -> container::Style {
    container::Style {
        background: Some(Background::Color(rgb(114, 170, 240))),
        ..container::Style::default()
    }
}

pub fn bay() -> container::Style {
    container::Style {
        background: Some(Background::Color(rgb(20, 25, 37))),
        border: Border {
            color: rgb(51, 59, 77),
            width: 1.0,
            radius: 5.0.into(),
        },
        ..container::Style::default()
    }
}

pub fn modulator_target(assigned: bool) -> container::Style {
    container::Style {
        background: Some(Background::Color(Color {
            r: 1.0,
            g: 0.78,
            b: 0.27,
            a: if assigned { 0.25 } else { 0.08 },
        })),
        border: Border {
            color: if assigned {
                rgb(255, 200, 70)
            } else {
                rgb(180, 150, 80)
            },
            width: if assigned { 2.0 } else { 1.5 },
            radius: 4.0.into(),
        },
        ..container::Style::default()
    }
}

pub fn readout() -> container::Style {
    container::Style {
        background: Some(Background::Color(rgb(31, 38, 52))),
        border: Border {
            color: rgb(63, 74, 95),
            width: 1.0,
            radius: 4.0.into(),
        },
        ..container::Style::default()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rgb_converts_bytes_to_color() {
        let color = rgb(255, 128, 0);
        assert_eq!(color.r, 1.0);
        assert_eq!(color.g, 128.0 / 255.0);
        assert_eq!(color.b, 0.0);
    }

    #[test]
    fn brighten_increases_color_values() {
        let base = Color::from_rgb(0.5, 0.5, 0.5);
        let brightened = brighten(base, 0.2);
        assert!(brightened.r > base.r);
        assert!(brightened.g > base.g);
        assert!(brightened.b > base.b);
    }

    #[test]
    fn brighten_clamps_at_1() {
        let base = Color::from_rgb(0.9, 0.9, 0.9);
        let brightened = brighten(base, 0.2);
        assert_eq!(brightened.r, 1.0);
        assert_eq!(brightened.g, 1.0);
        assert_eq!(brightened.b, 1.0);
    }

    #[test]
    fn darken_decreases_color_values() {
        let base = Color::from_rgb(0.5, 0.5, 0.5);
        let darkened = darken(base, 0.2);
        assert!(darkened.r < base.r);
        assert!(darkened.g < base.g);
        assert!(darkened.b < base.b);
    }

    #[test]
    fn darken_clamps_at_0() {
        let base = Color::from_rgb(0.1, 0.1, 0.1);
        let darkened = darken(base, 0.2);
        assert_eq!(darkened.r, 0.0);
        assert_eq!(darkened.g, 0.0);
        assert_eq!(darkened.b, 0.0);
    }

    #[test]
    fn strip_returns_style() {
        let style = strip(false, None);
        assert!(style.background.is_some());
        assert_eq!(style.border.width, 1.0);
    }

    #[test]
    fn strip_selected_has_thicker_border() {
        let unselected = strip(false, None);
        let selected = strip(true, None);
        assert!(selected.border.width > unselected.border.width);
    }

    #[test]
    fn bay_returns_style() {
        let style = bay();
        assert!(style.background.is_some());
        assert_eq!(style.border.width, 1.0);
    }

    #[test]
    fn readout_returns_style() {
        let style = readout();
        assert!(style.background.is_some());
        assert_eq!(style.border.width, 1.0);
    }
}