Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Copy link
Owner

@lukeyou05 lukeyou05 Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config section in the README is meant to track whatever is in the latest release, so we should revert everything here since it won't be released until later.

Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ global:
# inactive_color: the color of the inactive window's border
#
# Supported color types:
# - Solid: Use a hex code or "accent"
# - Solid: Use a hex code, "accent", or with_opacity() format
# Example:
# active_color: "#ffffff"
# OR
# active_color: "accent"
# OR
# active_color: "with_opacity(accent,40%)" # Windows accent color with 40% opacity
# OR
# active_color: "with_opacity(#ff0000,60%)" # Red color with 60% opacity
# NOTE: Instead of using hex with alpha like "#cff70f3D", you can use "with_opacity(#cff70f,24%)"
# - Gradient: Define colors and direction
# Example:
# active_color:
Expand Down
111 changes: 111 additions & 0 deletions src/colors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,13 +423,56 @@ fn get_accent_color(is_active_color: bool) -> D2D1_COLOR_F {
}

fn get_color_from_hex(hex: &str) -> D2D1_COLOR_F {
// Check if the string is in with_opacity(color,X%) format
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably move the parse_with_opacity() call outside get_color_from_hex() since the names are different now. I think the best way to do that would be to break out lines 426 - 432 into a separate function called something like get_color_with_opacity(), and then use that function in lines 85 and 108 (right next to the other get_accent_color() and get_color_from_hex() calls). That does lead to more repeated code but I could refactor that out in the future.

if let Some(with_opacity_content) = hex.strip_prefix("with_opacity(").and_then(|s| s.strip_suffix(")")) {
return parse_with_opacity(with_opacity_content).unwrap_or_else(|err| {
error!("could not parse with_opacity: {err}");
D2D1_COLOR_F::default()
});
}

let s = hex.strip_prefix("#").unwrap_or_default();
parse_hex(s).unwrap_or_else(|err| {
error!("could not parse hex: {err}");
D2D1_COLOR_F::default()
})
}

fn parse_with_opacity(s: &str) -> anyhow::Result<D2D1_COLOR_F> {
// Expected format: "color,X%" where color is "accent" or hex code, X is opacity percentage
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 2 {
return Err(anyhow!("invalid with_opacity format, expected 'color,X%': {s}"));
}

// Extract opacity percentage
let alpha_str = parts[1].trim();
if !alpha_str.ends_with('%') {
return Err(anyhow!("invalid alpha format, expected percentage: {alpha_str}"));
}

let alpha_percent = alpha_str
.strip_suffix('%')
.and_then(|s| s.parse::<f32>().ok())
.ok_or_else(|| anyhow!("invalid alpha percentage: {alpha_str}"))?;

// Get color based on the first parameter
let color_part = parts[0].trim();
let mut color = if color_part == "accent" {
// If accent is specified, get Windows accent color
get_accent_color(true)
} else {
// Otherwise parse as hex code
let hex = color_part.strip_prefix("#").unwrap_or(color_part);
parse_hex(hex)?
};

// Override transparency
color.a = alpha_percent / 100.0;

Ok(color)
}

fn parse_hex(s: &str) -> anyhow::Result<D2D1_COLOR_F> {
if !matches!(s.len(), 3 | 4 | 6 | 8) || !s[1..].chars().all(|c| c.is_ascii_hexdigit()) {
return Err(anyhow!("invalid hex: {s}"));
Expand Down Expand Up @@ -557,4 +600,72 @@ mod tests {

Ok(())
}

#[test]
fn test_with_opacity_accent() -> anyhow::Result<()> {
let color_brush_config = ColorBrushConfig::Solid("with_opacity(accent,40%)".to_string());
let color_brush = color_brush_config.to_color_brush(true);

if let ColorBrush::Solid(ref solid) = color_brush {
// We can't test exact color values since accent color depends on Windows settings
// But we can test that alpha is set correctly
assert_eq!(solid.color.a, 0.4);
} else {
panic!("created incorrect color brush");
}

Ok(())
}

#[test]
fn test_with_opacity_hex_code() -> anyhow::Result<()> {
let color_brush_config = ColorBrushConfig::Solid("with_opacity(#ff0000,60%)".to_string());
let color_brush = color_brush_config.to_color_brush(true);

if let ColorBrush::Solid(ref solid) = color_brush {
assert_eq!(solid.color.r, 1.0);
assert_eq!(solid.color.g, 0.0);
assert_eq!(solid.color.b, 0.0);
assert_eq!(solid.color.a, 0.6);
} else {
panic!("created incorrect color brush");
}

Ok(())
}

#[test]
fn test_with_opacity_hex_with_alpha_override() -> anyhow::Result<()> {
let color_brush_config = ColorBrushConfig::Solid("with_opacity(#ff000080,30%)".to_string());
let color_brush = color_brush_config.to_color_brush(true);

if let ColorBrush::Solid(ref solid) = color_brush {
assert_eq!(solid.color.r, 1.0);
assert_eq!(solid.color.g, 0.0);
assert_eq!(solid.color.b, 0.0);
// Alpha should be overridden to 30%
assert_eq!(solid.color.a, 0.3);
} else {
panic!("created incorrect color brush");
}

Ok(())
}

#[test]
fn test_with_opacity_without_hash() -> anyhow::Result<()> {
let color_brush_config = ColorBrushConfig::Solid("with_opacity(00ff00,50%)".to_string());
let color_brush = color_brush_config.to_color_brush(true);

if let ColorBrush::Solid(ref solid) = color_brush {
assert_eq!(solid.color.r, 0.0);
assert_eq!(solid.color.g, 1.0);
assert_eq!(solid.color.b, 0.0);
assert_eq!(solid.color.a, 0.5);
} else {
panic!("created incorrect color brush");
}

Ok(())
}
}
7 changes: 6 additions & 1 deletion src/resources/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,16 @@ global:
# inactive_color: the color of the inactive window's border
#
# Supported color types:
# - Solid: Use a hex code or "accent"
# - Solid: Use a hex code, "accent", or with_opacity() format
# Example:
# active_color: "#ffffff"
# OR
# active_color: "accent"
# OR
# active_color: "with_opacity(accent,40%)"
# OR
# active_color: "with_opacity(#ff0000,60%)"
# NOTE: Instead of using hex with alpha like "#cff70f3D", you can use "with_opacity(#cff70f,24%)"
# - Gradient: Define colors and direction
# Example:
# active_color:
Expand Down