PolyMeilex / sctk-adwaita

Adwaita-like SCTK Frame
MIT License
26 stars 19 forks source link

Window shadows #43

Closed Friz64 closed 9 months ago

Friz64 commented 10 months ago

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](https://github.com/PolyMeilex/sctk-adwaita/assets/20155479/d4a83ffa-824e-435e-bf21-bceefb72f006) 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](https://github.com/PolyMeilex/sctk-adwaita/assets/20155479/5df0498f-df10-471c-96b1-a5e7895947b9) Presented through winit.

Fixes #4.

last-partizan commented 9 months ago

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 commented 9 months ago

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

Friz64 commented 9 months ago

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: https://github.com/Friz64/winit/commit/b2bd287f24d436a9458f89f44ee95808e63435ad

Friz64 commented 9 months ago

Rebased.

Friz64 commented 9 months ago

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 commented 9 months ago

@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 commented 9 months ago

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 commented 9 months ago

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