Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add window transparency effect #864

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
refactor: change transparency to opacity
  • Loading branch information
jackssrt committed Dec 4, 2024
commit f28cae416ac0d9cc743775499f6e21960e50114b
8 changes: 4 additions & 4 deletions packages/wm/src/app_command.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ use crate::{
cycle_focus, disable_binding_mode, enable_binding_mode,
reload_config, shell_exec, toggle_pause,
},
Direction, LengthValue, RectDelta, TilingDirection, TransparencyValue,
Direction, LengthValue, OpacityValue, RectDelta, TilingDirection,
},
containers::{
commands::{
@@ -228,7 +228,7 @@ pub enum InvokeCommand {
},
SetTransparency {
#[clap(required = true, allow_hyphen_values = true)]
transparency: TransparencyValue,
opacity: OpacityValue,
},
ShellExec {
#[clap(long, action)]
@@ -606,10 +606,10 @@ impl InvokeCommand {
_ => Ok(()),
}
}
InvokeCommand::SetTransparency { transparency } => {
InvokeCommand::SetTransparency { opacity } => {
match subject_container.as_window_container() {
Ok(window) => {
_ = window.native().set_transparency(transparency.clone());
_ = window.native().set_opacity(opacity.clone());
Ok(())
}
_ => Ok(()),
22 changes: 10 additions & 12 deletions packages/wm/src/common/commands/platform_sync.rs
Original file line number Diff line number Diff line change
@@ -5,9 +5,7 @@ use tokio::task;
use tracing::warn;

use crate::{
common::{
platform::Platform, DisplayState, TransparencyUnit, TransparencyValue,
},
common::{platform::Platform, DisplayState, OpacityUnit, OpacityValue},
containers::{
traits::{CommonGetters, PositionGetters},
Container, WindowContainer,
@@ -293,19 +291,19 @@ fn apply_transparency_effect(
window: &WindowContainer,
effect_config: &WindowEffectConfig,
) {
_ = window.native().set_transparency(
if effect_config.transparency.enabled {
effect_config.transparency.transparency.clone()
_ = window
.native()
.set_opacity(if effect_config.transparency.enabled {
effect_config.transparency.opacity.clone()
} else {
// This code is only reached if the transparency effect is only
// enabled in one of the two window effect configurations. In
// this case, reset the transparency to default.
TransparencyValue {
amount: 0.0,
unit: TransparencyUnit::Exact,
// this case, reset the opacity to default.
OpacityValue {
amount: 255.0,
unit: OpacityUnit::Alpha,
delta_sign: false,
is_delta: false,
}
},
)
})
}
28 changes: 13 additions & 15 deletions packages/wm/src/common/platform/native_window.rs
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ use windows::{

use super::{iapplication_view_collection, iservice_provider, COM_INIT};
use crate::{
common::{Color, LengthValue, Memo, Rect, RectDelta, TransparencyValue},
common::{Color, LengthValue, Memo, OpacityValue, Rect, RectDelta},
user_config::{CornerStyle, HideMethod},
windows::WindowState,
};
@@ -398,9 +398,9 @@ impl NativeWindow {
Ok(())
}

pub fn set_transparency(
pub fn set_opacity(
&self,
transparency: TransparencyValue,
opacity_value: OpacityValue,
) -> anyhow::Result<()> {
// Make the window layered if it isn't already.
jackssrt marked this conversation as resolved.
Show resolved Hide resolved
let ex_style =
@@ -417,7 +417,7 @@ impl NativeWindow {
}
}

// Get the window's transparency information.
// Get the window's opacity information.
let mut previous_opacity = MaybeUninit::uninit();
let mut flag = MaybeUninit::uninit();
unsafe {
@@ -439,22 +439,20 @@ impl NativeWindow {
}

// Calculate the new opacity value.
let transparency_value = transparency.to_exact();
let new_opacity = if transparency.is_delta {
// Flip the sign of the delta to get the *opacity* delta.
// TODO: Use saturating_sub_signed when it's stable
if !transparency.delta_sign {
// -(-x) is x
previous_opacity.saturating_add(transparency_value)
let exact_opacity_value = opacity_value.to_exact();
let new_opacity = if opacity_value.is_delta {
// TODO: Enable feature for saturing_sub_signed
// TODO: Use a signed value for delta
if !opacity_value.delta_sign {
previous_opacity.saturating_sub(exact_opacity_value)
} else {
// -(x) is -x
previous_opacity.saturating_sub(transparency_value)
previous_opacity.saturating_add(exact_opacity_value)
}
} else {
255u8.saturating_sub(transparency_value)
exact_opacity_value
};

// Set new transparency if needed.
// Set the new opacity if needed.
if new_opacity != previous_opacity {
unsafe {
SetLayeredWindowAttributes(
58 changes: 29 additions & 29 deletions packages/wm/src/common/transparency_value.rs
Original file line number Diff line number Diff line change
@@ -5,65 +5,65 @@ use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct TransparencyValue {
pub struct OpacityValue {
pub amount: f32,
pub unit: TransparencyUnit,
pub unit: OpacityUnit,
pub is_delta: bool,
pub delta_sign: bool,
jackssrt marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TransparencyUnit {
Exact,
pub enum OpacityUnit {
Alpha,
Percentage,
}

impl TransparencyValue {
impl OpacityValue {
pub fn to_exact(&self) -> u8 {
match self.unit {
TransparencyUnit::Exact => self.amount as u8,
TransparencyUnit::Percentage => (self.amount * 255.0) as u8,
OpacityUnit::Alpha => self.amount as u8,
OpacityUnit::Percentage => (self.amount * 255.0) as u8,
}
}
}

impl Default for TransparencyValue {
impl Default for OpacityValue {
fn default() -> Self {
Self {
amount: 1.0,
unit: TransparencyUnit::Exact,
amount: 255.0,
unit: OpacityUnit::Alpha,
is_delta: false,
delta_sign: false,
}
}
}

impl FromStr for TransparencyValue {
impl FromStr for OpacityValue {
type Err = anyhow::Error;

/// Parses a string for a transparency value. The string can be a number
/// Parses a string for an opacity value. The string can be a number
/// or a percentage. If the string starts with a sign, the value is
/// interpreted as a delta.
///
/// Example:
/// ```
/// # use wm::common::{TransparencyValue, TransparencyUnit};
/// # use wm::common::{OpacityValue, OpacityUnit};
/// # use std::str::FromStr;
/// let check = TransparencyValue {
/// let check = OpacityValue {
/// amount: 0.75,
/// unit: TransparencyUnit::Percentage,
/// unit: OpacityUnit::Percentage,
/// is_delta: false,
/// delta_sign: false,
/// };
/// let parsed = TransparencyValue::from_str("75%");
/// let parsed = OpacityValue::from_str("75%");
/// assert_eq!(parsed.unwrap(), check);
/// ```
fn from_str(unparsed: &str) -> anyhow::Result<Self> {
let units_regex = Regex::new(r"([+-]?)(\d+)(%?)")?;

let err_msg = format!(
"Not a valid transparency value '{}'. Must be of format '255', '100%', '+10%' or '-128'.",
"Not a valid opacity value '{}'. Must be of format '255', '100%', '+10%' or '-128'.",
unparsed
);

@@ -79,8 +79,8 @@ impl FromStr for TransparencyValue {

let unit_str = captures.get(3).map_or("", |m| m.as_str());
let unit = match unit_str {
"" => TransparencyUnit::Exact,
"%" => TransparencyUnit::Percentage,
"" => OpacityUnit::Alpha,
"%" => OpacityUnit::Percentage,
_ => bail!(err_msg),
};

@@ -89,12 +89,12 @@ impl FromStr for TransparencyValue {
.and_then(|amount_str| f32::from_str(amount_str.into()).ok())
// Store percentage units as a fraction of 1.
.map(|amount| match unit {
TransparencyUnit::Exact => amount,
TransparencyUnit::Percentage => amount / 100.0,
OpacityUnit::Alpha => amount,
OpacityUnit::Percentage => amount / 100.0,
})
.context(err_msg.to_string())?;

Ok(TransparencyValue {
Ok(OpacityValue {
amount,
unit,
is_delta,
@@ -103,26 +103,26 @@ impl FromStr for TransparencyValue {
}
}

/// Deserialize a `TransparencyValue` from either a string or a struct.
impl<'de> Deserialize<'de> for TransparencyValue {
/// Deserialize an `OpacityValue` from either a string or a struct.
impl<'de> Deserialize<'de> for OpacityValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
enum TransparencyValueDe {
enum OpacityValueDe {
Struct {
amount: f32,
unit: TransparencyUnit,
unit: OpacityUnit,
is_delta: bool,
delta_sign: bool,
},
String(String),
}

match TransparencyValueDe::deserialize(deserializer)? {
TransparencyValueDe::Struct {
match OpacityValueDe::deserialize(deserializer)? {
OpacityValueDe::Struct {
amount,
unit,
is_delta,
@@ -133,7 +133,7 @@ impl<'de> Deserialize<'de> for TransparencyValue {
is_delta,
delta_sign,
}),
TransparencyValueDe::String(str) => {
OpacityValueDe::String(str) => {
Self::from_str(&str).map_err(serde::de::Error::custom)
}
}
6 changes: 3 additions & 3 deletions packages/wm/src/user_config.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};

use crate::{
app_command::InvokeCommand,
common::{Color, LengthValue, RectDelta, TransparencyValue},
common::{Color, LengthValue, OpacityValue, RectDelta},
containers::{traits::CommonGetters, WindowContainer},
monitors::Monitor,
windows::traits::WindowGetters,
@@ -569,9 +569,9 @@ pub struct TransparencyEffectConfig {
#[serde(default = "default_bool::<false>")]
pub enabled: bool,

/// Amount of transparency to apply.
/// The opacity to apply.
#[serde(default)]
pub transparency: TransparencyValue,
pub opacity: OpacityValue,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
8 changes: 4 additions & 4 deletions resources/assets/sample-config.yaml
Original file line number Diff line number Diff line change
@@ -77,9 +77,9 @@ window_effects:
# Change the transparency of the window.
transparency:
enabled: false
# Can be something like '15' or '5%' for slightly transparent windows
# '255' or '100%' is fully transparent (and, by consequence, unfocusable)
transparency: '5%'
# Can be something like '240' or '95%' for slightly transparent windows
# '0' or '0%' is fully transparent (and, by consequence, unfocusable)
opacity: '95%'

# Visual effects to apply to non-focused windows.
other_windows:
@@ -93,7 +93,7 @@ window_effects:
style: 'square'
transparency:
enabled: false
transparency: '10%'
opacity: '0%'

window_behavior:
# New windows are created in this state whenever possible.