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

Window shadows #43

Merged
merged 3 commits into from
Nov 27, 2023
Merged

Window shadows #43

merged 3 commits into from
Nov 27, 2023

Conversation

Friz64
Copy link
Contributor

@Friz64 Friz64 commented Nov 5, 2023

This PR consists of two parts that can be reviewed individually:

Decoupling the border size from the resize range

We place regions around the window in which the window borders are drawn. These regions currently stretch out as far as the resize range (e.g. how close to the window the pointer needs to be in order for it to be able to resize the window). We need to be able to draw shadows further than the resize range. This involves restricting the input region of the, now enlarged, border parts. Unfortunately, this is requires a breaking change in AdwaitaFrame::new as we need access to the wl_compositor for creating wl_region objects.

Generating and drawing the window shadow

I wanted the window shadows to be as close to libadwaita's window shadows as possible. But as far as I can see, its shadows are defined in (S)CSS, which is not an option for this crate. The solution to this I came up with is to measure libadwaita's shadow from a screenshot and use a curve-fitting algorithm to create an exponential function that lets us sample the alpha value of the shadow given the distance from the window's border.

To achieve this, I took these screenshots of a simple libadwaita window in both an active and inactive state (the shadow shrinks when the window is unfocused/inactive):

active.png inactive.png
active inactive

Next, I used this python script: https://gist.github.com/Friz64/ef941181f4d6aef82332f7066f984815

It's usage is as following:

./fit_libadwaita_shadow.py (active.png|inactive.png) [--plot]

When running it against active.png, it outputs the following:

a = 0.20650550475430265
b = 0.10461752988320006
c = -0.0005424462332409538
shadow size = 43

a, b, and c are the parameters for the exponential decay function a * exp(-b * x) + c. shadow size is the pixel distance from the window border at which the resulting u8 alpha value would round down to 0, making it meaningless to sample the function beyond this point.

Click to reveal: If desired, the script can also display a plot comparing the input and output.

active_plot

The X axis represents the distance from the window border in pixels (e.g. the input), the Y axis represents the alpha value between 0 and 1 (e.g. the output).

Armed with this function, we now have the ability to draw the window shadow with any given scale factor. To do this, we generate the following two pixmaps for each encountered pair of scale factor and active state by sampling it at each pixel.

pixmap name / what it's used for description visual explanation
side A strip of pixels, going from left to right with an increasing distance value. side
edges A square image, with the function sampled using the euclidean distance from the center.

We offset this distance by the corner radius, so that we can place the edge shadow around the rounded window corners.
edges

Then, all that's left is to copy these images (or parts of them) to the appropriate parts of the border, and we're done. We cache our work as much as possible in order to save resources.

Click to reveal: Screenshot of end result.

image
Presented through winit.

Fixes #4.

@Friz64 Friz64 force-pushed the window-shadows branch 2 times, most recently from 39a86d6 to 964fbf2 Compare November 24, 2023 22:23
@Friz64 Friz64 marked this pull request as ready for review November 25, 2023 03:12
@Friz64 Friz64 mentioned this pull request Nov 25, 2023
@last-partizan
Copy link
Contributor

Looks great! Thanks for this work!

I tried compiling neovide with this, but got an error:

error[E0061]: this function takes 6 arguments but 5 arguments were supplied
   --> /home/serg/.cargo/registry/src/index.crates.io-6f17d22bba15001f/winit-0.29.4/src/platform_impl/linux/wayland/window/state.rs:272:19
    |
272 |             match WinitFrame::new(
    |                   ^^^^^^^^^^^^^^^
...
275 |                 subcompositor.clone(),
    |                 --------------------- an argument of type `std::sync::Arc<CompositorState>` is missing
    |
note: associated function defined here
   --> /home/serg/.cargo/git/checkouts/sctk-adwaita-ef311f264e4fa479/4939ec0/src/lib.rs:97:12
    |
97  |     pub fn new(
    |            ^^^
help: provide the argument
    |
272 |             match WinitFrame::new(&self.window, shm, /* std::sync::Arc<CompositorState> */, subcompositor.clone(), self.queue_handle.clone(), into_sctk_adwaita_config(self.theme)) {
    |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For more information about this error, try `rustc --explain E0061`.
error: could not compile `winit` (lib) due to previous error
warning: build failed, waiting for other jobs to finish...

@last-partizan
Copy link
Contributor

Oh, nevermind. Looks like winit needs to pass that argument, i changed it by hand. And it works! Looks great!

@Friz64
Copy link
Contributor Author

Friz64 commented Nov 25, 2023

Right, anyone who wants to try this in their project, and is already using winit 0.29, can put this in their Cargo.toml:

[patch.crates-io]
winit = { git = "https://github.com/Friz64/winit.git", branch = "window-shadows-0.29.4" }

The patch is very simple: Friz64/winit@b2bd287

@Friz64
Copy link
Contributor Author

Friz64 commented Nov 27, 2023

Rebased.

Copy link
Owner

@PolyMeilex PolyMeilex left a comment

Choose a reason for hiding this comment

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

Looks awesome! Thanks!

@PolyMeilex PolyMeilex merged commit 233b3e2 into PolyMeilex:master Nov 27, 2023
6 checks passed
@Friz64 Friz64 deleted the window-shadows branch November 27, 2023 20:02
@Friz64
Copy link
Contributor Author

Friz64 commented Nov 27, 2023

With this merged, along with my other PRs (#44, #45, #46, #47), the major parity issues with GTK/libadwaita are, in my humble opinion, resolved 🎉. Who needs libdecor, right?

Thank you for this library!

@last-partizan
Copy link
Contributor

@PolyMeilex can you please make a release?

And another unrelated question. Is there a reason for having circular buttons, instead of circular buttons with same color as background, as in Adwaita? I can try fixing it, if this a desired change.

image

@PolyMeilex
Copy link
Owner

can you please make a release?

Sure thing, will do.

Is there a reason for having circular buttons, instead of circular buttons with same color as background, as in Adwaita?

They are not:

Regular headerbar style:
image
Flat Headerbar style:
image

You are probably referring to GTK4 style rather than libadwaita style

@Aalivexy
Copy link

Cool job! It looks really great! Who needs libdecor, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Window shadows?
4 participants