mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Implement window resize animations
This commit is contained in:
Generated
+2
-2
@@ -3115,7 +3115,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
[[package]]
|
||||
name = "smithay"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#e237b077bd922e17849eff91ba05853c7a68f958"
|
||||
source = "git+https://github.com/Smithay/smithay.git#c5e9a697e41f50dc56b918d9ef1e3d2e52c84ac0"
|
||||
dependencies = [
|
||||
"appendlist",
|
||||
"bitflags 2.5.0",
|
||||
@@ -3187,7 +3187,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "smithay-drm-extras"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#e237b077bd922e17849eff91ba05853c7a68f958"
|
||||
source = "git+https://github.com/Smithay/smithay.git#c5e9a697e41f50dc56b918d9ef1e3d2e52c84ac0"
|
||||
dependencies = [
|
||||
"drm",
|
||||
"edid-rs",
|
||||
|
||||
@@ -489,6 +489,8 @@ pub struct Animations {
|
||||
pub window_open: Animation,
|
||||
#[knuffel(child, default = Animation::default_window_close())]
|
||||
pub window_close: Animation,
|
||||
#[knuffel(child, default = Animation::default_window_resize())]
|
||||
pub window_resize: Animation,
|
||||
#[knuffel(child, default = Animation::default_config_notification_open_close())]
|
||||
pub config_notification_open_close: Animation,
|
||||
}
|
||||
@@ -503,6 +505,7 @@ impl Default for Animations {
|
||||
window_movement: Animation::default_window_movement(),
|
||||
window_open: Animation::default_window_open(),
|
||||
window_close: Animation::default_window_close(),
|
||||
window_resize: Animation::default_window_resize(),
|
||||
config_notification_open_close: Animation::default_config_notification_open_close(),
|
||||
}
|
||||
}
|
||||
@@ -592,6 +595,17 @@ impl Animation {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_window_resize() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
||||
@@ -20,7 +20,7 @@ impl Tile {
|
||||
pub fn freeform(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::freeform(0);
|
||||
let mut rv = Self::with_window(window);
|
||||
rv.tile.request_tile_size(size);
|
||||
rv.tile.request_tile_size(size, false);
|
||||
rv.window.communicate();
|
||||
rv
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl Tile {
|
||||
pub fn fixed_size(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
let mut rv = Self::with_window(window);
|
||||
rv.tile.request_tile_size(size);
|
||||
rv.tile.request_tile_size(size, false);
|
||||
rv.window.communicate();
|
||||
rv
|
||||
}
|
||||
@@ -37,7 +37,7 @@ impl Tile {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
window.set_csd_shadow_width(64);
|
||||
let mut rv = Self::with_window(window);
|
||||
rv.tile.request_tile_size(size);
|
||||
rv.tile.request_tile_size(size, false);
|
||||
rv.window.communicate();
|
||||
rv
|
||||
}
|
||||
@@ -84,7 +84,8 @@ impl Tile {
|
||||
|
||||
impl TestCase for Tile {
|
||||
fn resize(&mut self, width: i32, height: i32) {
|
||||
self.tile.request_tile_size(Size::from((width, height)));
|
||||
self.tile
|
||||
.request_tile_size(Size::from((width, height)), false);
|
||||
self.window.communicate();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,23 +13,23 @@ pub struct Window {
|
||||
|
||||
impl Window {
|
||||
pub fn freeform(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::freeform(0);
|
||||
window.request_size(size);
|
||||
let mut window = TestWindow::freeform(0);
|
||||
window.request_size(size, false);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn fixed_size(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
window.request_size(size);
|
||||
let mut window = TestWindow::fixed_size(0);
|
||||
window.request_size(size, false);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn fixed_size_with_csd_shadow(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
let mut window = TestWindow::fixed_size(0);
|
||||
window.set_csd_shadow_width(64);
|
||||
window.request_size(size);
|
||||
window.request_size(size, false);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
@@ -37,7 +37,7 @@ impl Window {
|
||||
|
||||
impl TestCase for Window {
|
||||
fn resize(&mut self, width: i32, height: i32) {
|
||||
self.window.request_size(Size::from((width, height)));
|
||||
self.window.request_size(Size::from((width, height)), false);
|
||||
self.window.communicate();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ mod imp {
|
||||
use anyhow::{ensure, Context};
|
||||
use gtk::gdk;
|
||||
use gtk::prelude::*;
|
||||
use niri::render_helpers::shaders;
|
||||
use niri::render_helpers::{resources, shaders};
|
||||
use niri::utils::get_monotonic_time;
|
||||
use smithay::backend::egl::ffi::egl;
|
||||
use smithay::backend::egl::EGLContext;
|
||||
@@ -194,6 +194,7 @@ mod imp {
|
||||
let mut renderer = GlesRenderer::with_capabilities(egl_context, capabilities)
|
||||
.context("error creating GlesRenderer")?;
|
||||
|
||||
resources::init(&mut renderer);
|
||||
shaders::init(&mut renderer);
|
||||
|
||||
Ok(renderer)
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::cell::RefCell;
|
||||
use std::cmp::{max, min};
|
||||
use std::rc::Rc;
|
||||
|
||||
use niri::layout::{LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot};
|
||||
use niri::layout::{
|
||||
AnimationSnapshot, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
|
||||
};
|
||||
use niri::render_helpers::renderer::NiriRenderer;
|
||||
use niri::render_helpers::{RenderSnapshot, RenderTarget};
|
||||
use niri::window::ResolvedWindowRules;
|
||||
@@ -177,7 +179,7 @@ impl LayoutElement for TestWindow {
|
||||
RenderSnapshot::default()
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
fn request_size(&mut self, size: Size<i32, Logical>, _animate: bool) {
|
||||
self.inner.borrow_mut().requested_size = Some(size);
|
||||
self.inner.borrow_mut().pending_fullscreen = false;
|
||||
}
|
||||
@@ -214,7 +216,7 @@ impl LayoutElement for TestWindow {
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
fn send_pending_configure(&self) {}
|
||||
fn send_pending_configure(&mut self) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
@@ -230,4 +232,12 @@ impl LayoutElement for TestWindow {
|
||||
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
|
||||
&EMPTY
|
||||
}
|
||||
|
||||
fn animation_snapshot(&self) -> Option<&AnimationSnapshot> {
|
||||
None
|
||||
}
|
||||
|
||||
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -59,7 +59,7 @@ use super::{IpcOutputMap, RenderResult};
|
||||
use crate::frame_clock::FrameClock;
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
use crate::render_helpers::renderer::AsGlesRenderer;
|
||||
use crate::render_helpers::{shaders, RenderTarget};
|
||||
use crate::render_helpers::{resources, shaders, RenderTarget};
|
||||
use crate::utils::{get_monotonic_time, logical_output};
|
||||
|
||||
const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888];
|
||||
@@ -490,6 +490,7 @@ impl Tty {
|
||||
warn!("error binding wl-display in EGL: {err:?}");
|
||||
}
|
||||
|
||||
resources::init(renderer.as_gles_renderer());
|
||||
shaders::init(renderer.as_gles_renderer());
|
||||
|
||||
// Create the dmabuf global.
|
||||
|
||||
@@ -19,7 +19,7 @@ use smithay::reexports::winit::window::WindowBuilder;
|
||||
|
||||
use super::{IpcOutputMap, RenderResult};
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
use crate::render_helpers::{shaders, RenderTarget};
|
||||
use crate::render_helpers::{resources, shaders, RenderTarget};
|
||||
use crate::utils::{get_monotonic_time, logical_output};
|
||||
|
||||
pub struct Winit {
|
||||
@@ -130,6 +130,7 @@ impl Winit {
|
||||
warn!("error binding renderer wl_display: {err}");
|
||||
}
|
||||
|
||||
resources::init(renderer);
|
||||
shaders::init(renderer);
|
||||
|
||||
niri.add_output(self.output.clone(), None);
|
||||
|
||||
@@ -815,16 +815,34 @@ fn unconstrain_with_padding(
|
||||
|
||||
pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId {
|
||||
add_pre_commit_hook::<State, _>(toplevel.wl_surface(), move |state, _dh, surface| {
|
||||
let _span = tracy_client::span!("mapped toplevel pre-commit");
|
||||
|
||||
let Some((mapped, _)) = state.niri.layout.find_window_and_output_mut(surface) else {
|
||||
error!("pre-commit hook for mapped surfaces must be removed upon unmapping");
|
||||
return;
|
||||
};
|
||||
|
||||
let got_unmapped = with_states(surface, |states| {
|
||||
let attrs = states.cached_state.current::<SurfaceAttributes>();
|
||||
matches!(attrs.buffer, Some(BufferAssignment::Removed))
|
||||
let (got_unmapped, commit_serial) = with_states(surface, |states| {
|
||||
let attrs = states.cached_state.pending::<SurfaceAttributes>();
|
||||
let got_unmapped = matches!(attrs.buffer, Some(BufferAssignment::Removed));
|
||||
|
||||
let role = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
(got_unmapped, role.configure_serial)
|
||||
});
|
||||
|
||||
let animate = if let Some(serial) = commit_serial {
|
||||
mapped.should_animate_commit(serial)
|
||||
} else {
|
||||
error!("commit on a mapped surface without a configured serial");
|
||||
false
|
||||
};
|
||||
|
||||
if got_unmapped {
|
||||
state.backend.with_primary_renderer(|renderer| {
|
||||
mapped.render_and_store_snapshot(renderer);
|
||||
@@ -832,6 +850,15 @@ pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId
|
||||
} else {
|
||||
// The toplevel remains mapped; clear any cached render snapshot.
|
||||
let _ = mapped.take_last_render();
|
||||
|
||||
if animate {
|
||||
state.backend.with_primary_renderer(|renderer| {
|
||||
mapped.store_animation_snapshot(renderer);
|
||||
});
|
||||
|
||||
let window = mapped.window.clone();
|
||||
state.niri.layout.prepare_for_resize_animation(&window);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+52
-4
@@ -60,6 +60,9 @@ pub mod monitor;
|
||||
pub mod tile;
|
||||
pub mod workspace;
|
||||
|
||||
/// Size changes up to this many pixels don't animate.
|
||||
pub const RESIZE_ANIMATION_THRESHOLD: i32 = 10;
|
||||
|
||||
niri_render_elements! {
|
||||
LayoutElementRenderElement<R> => {
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
@@ -70,6 +73,15 @@ niri_render_elements! {
|
||||
pub type LayoutElementRenderSnapshot =
|
||||
RenderSnapshot<BakedBuffer<TextureBuffer<GlesTexture>>, BakedBuffer<SolidColorBuffer>>;
|
||||
|
||||
/// Snapshot of an element for animation.
|
||||
#[derive(Debug)]
|
||||
pub struct AnimationSnapshot {
|
||||
/// Snapshot of the render.
|
||||
pub render: LayoutElementRenderSnapshot,
|
||||
/// Visual size of the element at the point of the snapshot.
|
||||
pub size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
pub trait LayoutElement {
|
||||
/// Type that can be used as a unique ID of this element.
|
||||
type Id: PartialEq;
|
||||
@@ -108,7 +120,7 @@ pub trait LayoutElement {
|
||||
|
||||
fn take_last_render(&self) -> LayoutElementRenderSnapshot;
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>);
|
||||
fn request_size(&mut self, size: Size<i32, Logical>, animate: bool);
|
||||
fn request_fullscreen(&self, size: Size<i32, Logical>);
|
||||
fn min_size(&self) -> Size<i32, Logical>;
|
||||
fn max_size(&self) -> Size<i32, Logical>;
|
||||
@@ -121,7 +133,7 @@ pub trait LayoutElement {
|
||||
fn set_activated(&mut self, active: bool);
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
||||
|
||||
fn send_pending_configure(&self);
|
||||
fn send_pending_configure(&mut self);
|
||||
|
||||
/// Whether the element is currently fullscreen.
|
||||
///
|
||||
@@ -137,6 +149,9 @@ pub trait LayoutElement {
|
||||
|
||||
/// Runs periodic clean-up tasks.
|
||||
fn refresh(&self);
|
||||
|
||||
fn animation_snapshot(&self) -> Option<&AnimationSnapshot>;
|
||||
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -1787,6 +1802,31 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_for_resize_animation(&mut self, window: &W::Id) {
|
||||
let _span = tracy_client::span!("Layout::prepare_for_resize_animation");
|
||||
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mut mon.workspaces {
|
||||
if ws.has_window(window) {
|
||||
ws.prepare_for_resize_animation(window);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||
for ws in workspaces {
|
||||
if ws.has_window(window) {
|
||||
ws.prepare_for_resize_animation(window);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
let _span = tracy_client::span!("Layout::refresh");
|
||||
|
||||
@@ -1930,7 +1970,7 @@ mod tests {
|
||||
RenderSnapshot::default()
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
fn request_size(&mut self, size: Size<i32, Logical>, _animate: bool) {
|
||||
self.0.requested_size.set(Some(size));
|
||||
self.0.pending_fullscreen.set(false);
|
||||
}
|
||||
@@ -1967,7 +2007,7 @@ mod tests {
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
fn send_pending_configure(&self) {}
|
||||
fn send_pending_configure(&mut self) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
@@ -1983,6 +2023,14 @@ mod tests {
|
||||
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
|
||||
&EMPTY
|
||||
}
|
||||
|
||||
fn animation_snapshot(&self) -> Option<&AnimationSnapshot> {
|
||||
None
|
||||
}
|
||||
|
||||
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
|
||||
|
||||
+334
-24
@@ -1,21 +1,31 @@
|
||||
use std::cell::OnceCell;
|
||||
use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::BlockOutFrom;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::utils::RescaleRenderElement;
|
||||
use smithay::backend::renderer::element::{Element, Kind};
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use super::focus_ring::{FocusRing, FocusRingRenderElement};
|
||||
use super::{LayoutElement, LayoutElementRenderElement, Options};
|
||||
use super::{
|
||||
AnimationSnapshot, LayoutElement, LayoutElementRenderElement, Options,
|
||||
RESIZE_ANIMATION_THRESHOLD,
|
||||
};
|
||||
use crate::animation::Animation;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::crossfade::CrossfadeRenderElement;
|
||||
use crate::render_helpers::offscreen::OffscreenRenderElement;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::{RenderSnapshot, RenderTarget, ToRenderElement};
|
||||
use crate::render_helpers::shaders::Shaders;
|
||||
use crate::render_helpers::{
|
||||
render_to_encompassing_texture, RenderSnapshot, RenderTarget, ToRenderElement,
|
||||
};
|
||||
|
||||
/// Toplevel window with decorations.
|
||||
#[derive(Debug)]
|
||||
@@ -47,6 +57,12 @@ pub struct Tile<W: LayoutElement> {
|
||||
/// The animation upon opening a window.
|
||||
open_animation: Option<Animation>,
|
||||
|
||||
/// The animation of the window resizing.
|
||||
resize_animation: Option<ResizeAnimation>,
|
||||
|
||||
/// The animation of a tile visually moving.
|
||||
move_animation: Option<MoveAnimation>,
|
||||
|
||||
/// Configurable properties of the layout.
|
||||
options: Rc<Options>,
|
||||
}
|
||||
@@ -57,17 +73,41 @@ niri_render_elements! {
|
||||
FocusRing = FocusRingRenderElement,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
Offscreen = RescaleRenderElement<OffscreenRenderElement>,
|
||||
Crossfade = CrossfadeRenderElement,
|
||||
}
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
TileSnapshotContentsRenderElement => {
|
||||
Texture = PrimaryGpuTextureRenderElement,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
}
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
TileSnapshotRenderElement => {
|
||||
Texture = PrimaryGpuTextureRenderElement,
|
||||
Contents = RescaleRenderElement<TileSnapshotContentsRenderElement>,
|
||||
FocusRing = FocusRingRenderElement,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ResizeAnimation {
|
||||
anim: Animation,
|
||||
size_from: Size<i32, Logical>,
|
||||
snapshot: AnimationSnapshot,
|
||||
/// Snapshot rendered into a texture (happens lazily).
|
||||
snapshot_texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
|
||||
snapshot_blocked_out_texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MoveAnimation {
|
||||
anim: Animation,
|
||||
from: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Tile<W> {
|
||||
pub fn new(window: W, options: Rc<Options>) -> Self {
|
||||
Self {
|
||||
@@ -78,6 +118,8 @@ impl<W: LayoutElement> Tile<W> {
|
||||
fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]),
|
||||
fullscreen_size: Default::default(),
|
||||
open_animation: None,
|
||||
resize_animation: None,
|
||||
move_animation: None,
|
||||
options,
|
||||
}
|
||||
}
|
||||
@@ -93,16 +135,76 @@ impl<W: LayoutElement> Tile<W> {
|
||||
if self.fullscreen_size != Size::from((0, 0)) {
|
||||
self.is_fullscreen = self.window.is_fullscreen();
|
||||
}
|
||||
|
||||
if let Some(animate_from) = self.window.take_animation_snapshot() {
|
||||
let size_from = if let Some(resize) = self.resize_animation.take() {
|
||||
// Compute like in animated_window_size(), but using the snapshot geometry (since
|
||||
// the current one is already overwritten).
|
||||
let mut size = animate_from.size;
|
||||
|
||||
let val = resize.anim.value();
|
||||
let size_from = resize.size_from;
|
||||
|
||||
size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32;
|
||||
size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32;
|
||||
|
||||
size
|
||||
} else {
|
||||
animate_from.size
|
||||
};
|
||||
|
||||
let change = self.window.size().to_point() - size_from.to_point();
|
||||
let change = max(change.x.abs(), change.y.abs());
|
||||
if change > RESIZE_ANIMATION_THRESHOLD {
|
||||
let anim = Animation::new(
|
||||
0.,
|
||||
1.,
|
||||
0.,
|
||||
self.options.animations.window_resize,
|
||||
niri_config::Animation::default_window_resize(),
|
||||
);
|
||||
self.resize_animation = Some(ResizeAnimation {
|
||||
anim,
|
||||
size_from,
|
||||
snapshot: animate_from,
|
||||
snapshot_texture: OnceCell::new(),
|
||||
snapshot_blocked_out_texture: OnceCell::new(),
|
||||
});
|
||||
} else {
|
||||
self.resize_animation = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
||||
if let Some(anim) = &mut self.open_animation {
|
||||
anim.set_current_time(current_time);
|
||||
if anim.is_done() {
|
||||
self.open_animation = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(resize) = &mut self.resize_animation {
|
||||
resize.anim.set_current_time(current_time);
|
||||
if resize.anim.is_done() {
|
||||
self.resize_animation = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(move_) = &mut self.move_animation {
|
||||
move_.anim.set_current_time(current_time);
|
||||
if move_.anim.is_done() {
|
||||
self.move_animation = None;
|
||||
}
|
||||
}
|
||||
|
||||
let draw_border_with_background = self
|
||||
.window
|
||||
.rules()
|
||||
.draw_border_with_background
|
||||
.unwrap_or_else(|| !self.window.has_ssd());
|
||||
self.border
|
||||
.update(self.window.size(), !draw_border_with_background);
|
||||
.update(self.animated_window_size(), !draw_border_with_background);
|
||||
self.border.set_active(is_active);
|
||||
|
||||
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
|
||||
@@ -111,22 +213,24 @@ impl<W: LayoutElement> Tile<W> {
|
||||
draw_border_with_background
|
||||
};
|
||||
self.focus_ring
|
||||
.update(self.tile_size(), !draw_focus_ring_with_background);
|
||||
.update(self.animated_tile_size(), !draw_focus_ring_with_background);
|
||||
self.focus_ring.set_active(is_active);
|
||||
|
||||
match &mut self.open_animation {
|
||||
Some(anim) => {
|
||||
anim.set_current_time(current_time);
|
||||
if anim.is_done() {
|
||||
self.open_animation = None;
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn are_animations_ongoing(&self) -> bool {
|
||||
self.open_animation.is_some()
|
||||
|| self.resize_animation.is_some()
|
||||
|| self.move_animation.is_some()
|
||||
}
|
||||
|
||||
pub fn render_offset(&self) -> Point<i32, Logical> {
|
||||
let mut offset = Point::from((0., 0.));
|
||||
|
||||
if let Some(move_) = &self.move_animation {
|
||||
offset += move_.from.to_f64().upscale(move_.anim.value());
|
||||
}
|
||||
|
||||
offset.to_i32_round()
|
||||
}
|
||||
|
||||
pub fn start_open_animation(&mut self) {
|
||||
@@ -143,6 +247,24 @@ impl<W: LayoutElement> Tile<W> {
|
||||
&self.open_animation
|
||||
}
|
||||
|
||||
pub fn resize_animation(&self) -> Option<&Animation> {
|
||||
self.resize_animation.as_ref().map(|resize| &resize.anim)
|
||||
}
|
||||
|
||||
pub fn animate_move_from_with_config(
|
||||
&mut self,
|
||||
from: Point<i32, Logical>,
|
||||
config: niri_config::Animation,
|
||||
default: niri_config::Animation,
|
||||
) {
|
||||
let current_offset = self.render_offset();
|
||||
|
||||
self.move_animation = Some(MoveAnimation {
|
||||
anim: Animation::new(1., 0., 0., config, default),
|
||||
from: from + current_offset,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn window(&self) -> &W {
|
||||
&self.window
|
||||
}
|
||||
@@ -217,6 +339,39 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.window.size()
|
||||
}
|
||||
|
||||
fn animated_window_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.size();
|
||||
|
||||
if let Some(resize) = &self.resize_animation {
|
||||
let val = resize.anim.value();
|
||||
let size_from = resize.size_from;
|
||||
|
||||
size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32;
|
||||
size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32;
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
fn animated_tile_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.animated_window_size();
|
||||
|
||||
if self.is_fullscreen {
|
||||
// Normally we'd just return the fullscreen size here, but this makes things a bit
|
||||
// nicer if a fullscreen window is bigger than the fullscreen size for some reason.
|
||||
size.w = max(size.w, self.fullscreen_size.w);
|
||||
size.h = max(size.h, self.fullscreen_size.h);
|
||||
return size;
|
||||
}
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
size.w = size.w.saturating_add(width * 2);
|
||||
size.h = size.h.saturating_add(width * 2);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
let mut loc = Point::from((0, 0));
|
||||
loc += self.window_loc();
|
||||
@@ -234,7 +389,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
activation_region.to_f64().contains(point)
|
||||
}
|
||||
|
||||
pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>) {
|
||||
pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>, animate: bool) {
|
||||
// Can't go through effective_border_width() because we might be fullscreen.
|
||||
if !self.border.is_off() {
|
||||
let width = self.border.width();
|
||||
@@ -242,7 +397,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
size.h = max(1, size.h - width * 2);
|
||||
}
|
||||
|
||||
self.window.request_size(size);
|
||||
self.window.request_size(size, animate);
|
||||
}
|
||||
|
||||
pub fn tile_width_for_window_width(&self, size: i32) -> i32 {
|
||||
@@ -330,11 +485,85 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.)
|
||||
};
|
||||
|
||||
let rv = self
|
||||
.window
|
||||
.render(renderer, location + self.window_loc(), scale, alpha, target)
|
||||
let window_loc = self.window_loc();
|
||||
let window_size = self.window_size();
|
||||
let animated_window_size = self.animated_window_size();
|
||||
let window_render_loc = location + window_loc;
|
||||
let area = Rectangle::from_loc_and_size(window_render_loc, animated_window_size);
|
||||
|
||||
let gles_renderer = renderer.as_gles_renderer();
|
||||
|
||||
// If we're resizing, try to render a crossfade, or a fallback.
|
||||
let mut crossfade = None;
|
||||
let mut crossfade_fallback = None;
|
||||
|
||||
if let Some(resize) = &self.resize_animation {
|
||||
if Shaders::get(gles_renderer).crossfade.is_some() {
|
||||
if let Some(texture_from) = resize.rendered_texture(gles_renderer, scale, target) {
|
||||
let window_elements =
|
||||
self.window
|
||||
.render(gles_renderer, Point::from((0, 0)), scale, 1., target);
|
||||
let current = render_to_encompassing_texture(
|
||||
gles_renderer,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Fourcc::Abgr8888,
|
||||
&window_elements,
|
||||
)
|
||||
.map_err(|err| warn!("error rendering window to texture: {err:?}"))
|
||||
.ok();
|
||||
|
||||
if let Some((texture_current, _sync_point, texture_current_geo)) = current {
|
||||
let elem = CrossfadeRenderElement::new(
|
||||
gles_renderer,
|
||||
area,
|
||||
scale,
|
||||
texture_from.clone(),
|
||||
resize.snapshot.size,
|
||||
(texture_current, texture_current_geo),
|
||||
window_size,
|
||||
resize.anim.clamped_value().clamp(0., 1.) as f32,
|
||||
alpha,
|
||||
)
|
||||
.expect("we checked the crossfade shader above");
|
||||
self.window
|
||||
.set_offscreen_element_id(Some(elem.id().clone()));
|
||||
crossfade = Some(elem.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if crossfade.is_none() {
|
||||
let fallback_buffer = SolidColorBuffer::new(area.size, [1., 0., 0., 1.]);
|
||||
crossfade_fallback = Some(
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&fallback_buffer,
|
||||
area.loc.to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
self.window.set_offscreen_element_id(None);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not resizing, render the window itself.
|
||||
let mut window = None;
|
||||
if crossfade.is_none() && crossfade_fallback.is_none() {
|
||||
window = Some(
|
||||
self.window
|
||||
.render(renderer, window_render_loc, scale, alpha, target)
|
||||
.into_iter()
|
||||
.map(Into::into);
|
||||
.map(Into::into),
|
||||
);
|
||||
}
|
||||
|
||||
let rv = crossfade
|
||||
.into_iter()
|
||||
.chain(crossfade_fallback)
|
||||
.chain(window.into_iter().flatten());
|
||||
|
||||
let elem = self.effective_border_width().map(|width| {
|
||||
self.border
|
||||
@@ -422,7 +651,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
contents: Vec<C>,
|
||||
) -> Vec<TileSnapshotRenderElement>
|
||||
where
|
||||
E: Into<TileSnapshotRenderElement>,
|
||||
E: Into<TileSnapshotContentsRenderElement>,
|
||||
C: ToRenderElement<RenderElement = E>,
|
||||
{
|
||||
let alpha = if self.is_fullscreen {
|
||||
@@ -431,10 +660,18 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.)
|
||||
};
|
||||
|
||||
let window_size = self.window_size();
|
||||
let animated_window_size = self.animated_window_size();
|
||||
let animated_scale = animated_window_size.to_f64() / window_size.to_f64();
|
||||
|
||||
let mut rv = vec![];
|
||||
|
||||
for baked in contents {
|
||||
let elem = baked.to_render_element(self.window_loc(), scale, alpha, Kind::Unspecified);
|
||||
let elem: TileSnapshotContentsRenderElement = elem.into();
|
||||
|
||||
let origin = self.window_loc().to_physical_precise_round(scale);
|
||||
let elem = RescaleRenderElement::from_element(elem, origin, animated_scale);
|
||||
rv.push(elem.into());
|
||||
}
|
||||
|
||||
@@ -483,3 +720,76 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResizeAnimation {
|
||||
fn rendered_texture(
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
scale: Scale<f64>,
|
||||
target: RenderTarget,
|
||||
) -> &Option<(GlesTexture, Rectangle<i32, Physical>)> {
|
||||
let block_out = match self.snapshot.render.block_out_from {
|
||||
None => false,
|
||||
Some(BlockOutFrom::Screencast) => target == RenderTarget::Screencast,
|
||||
Some(BlockOutFrom::ScreenCapture) => target != RenderTarget::Output,
|
||||
};
|
||||
|
||||
if block_out {
|
||||
self.snapshot_blocked_out_texture.get_or_init(|| {
|
||||
let _span = tracy_client::span!("ResizeAnimation::rendered_texture");
|
||||
|
||||
let elements: Vec<_> = self
|
||||
.snapshot
|
||||
.render
|
||||
.blocked_out_contents
|
||||
.iter()
|
||||
.map(|baked| {
|
||||
baked.to_render_element(Point::from((0, 0)), scale, 1., Kind::Unspecified)
|
||||
})
|
||||
.collect();
|
||||
|
||||
match render_to_encompassing_texture(
|
||||
renderer,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Fourcc::Abgr8888,
|
||||
&elements,
|
||||
) {
|
||||
Ok((texture, _sync_point, geo)) => Some((texture, geo)),
|
||||
Err(err) => {
|
||||
warn!("error rendering snapshot to texture: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.snapshot_texture.get_or_init(|| {
|
||||
let _span = tracy_client::span!("ResizeAnimation::rendered_texture");
|
||||
|
||||
let elements: Vec<_> = self
|
||||
.snapshot
|
||||
.render
|
||||
.contents
|
||||
.iter()
|
||||
.map(|baked| {
|
||||
baked.to_render_element(Point::from((0, 0)), scale, 1., Kind::Unspecified)
|
||||
})
|
||||
.collect();
|
||||
|
||||
match render_to_encompassing_texture(
|
||||
renderer,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Fourcc::Abgr8888,
|
||||
&elements,
|
||||
) {
|
||||
Ok((texture, _sync_point, geo)) => Some((texture, geo)),
|
||||
Err(err) => {
|
||||
warn!("error rendering snapshot to texture: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+105
-24
@@ -190,6 +190,9 @@ pub struct Column<W: LayoutElement> {
|
||||
/// Animation of the render offset during window swapping.
|
||||
move_animation: Option<Animation>,
|
||||
|
||||
/// Width right before a resize animation on one of the windows of the column.
|
||||
pub width_before_resize: Option<i32>,
|
||||
|
||||
/// Latest known view size for this column's workspace.
|
||||
view_size: Size<i32, Logical>,
|
||||
|
||||
@@ -815,7 +818,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
|
||||
column.active_tile_idx = min(column.active_tile_idx, column.tiles.len() - 1);
|
||||
column.update_tile_sizes();
|
||||
column.update_tile_sizes(false);
|
||||
|
||||
window
|
||||
}
|
||||
@@ -879,16 +882,48 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
|
||||
pub fn update_window(&mut self, window: &W::Id) {
|
||||
let (idx, column) = self
|
||||
let (col_idx, column) = self
|
||||
.columns
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(_, col)| col.contains(window))
|
||||
.unwrap();
|
||||
column.update_window(window);
|
||||
column.update_tile_sizes();
|
||||
let tile_idx = column
|
||||
.tiles
|
||||
.iter()
|
||||
.position(|tile| tile.window().id() == window)
|
||||
.unwrap();
|
||||
|
||||
if idx == self.active_column_idx
|
||||
let offset = column
|
||||
.width_before_resize
|
||||
.take()
|
||||
.map_or(0, |prev| prev - column.width());
|
||||
|
||||
column.update_window(window);
|
||||
column.update_tile_sizes(false);
|
||||
|
||||
// Move other columns in tandem with resizing.
|
||||
if column.tiles[tile_idx].resize_animation().is_some() && offset != 0 {
|
||||
if self.active_column_idx <= col_idx {
|
||||
for col in &mut self.columns[col_idx + 1..] {
|
||||
col.animate_move_from_with_config(
|
||||
offset,
|
||||
self.options.animations.window_resize,
|
||||
niri_config::Animation::default_window_resize(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for col in &mut self.columns[..col_idx] {
|
||||
col.animate_move_from_with_config(
|
||||
-offset,
|
||||
self.options.animations.window_resize,
|
||||
niri_config::Animation::default_window_resize(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if col_idx == self.active_column_idx
|
||||
&& !matches!(self.view_offset_adj, Some(ViewOffsetAdjustment::Gesture(_)))
|
||||
{
|
||||
// We might need to move the view to ensure the resized window is still visible.
|
||||
@@ -896,7 +931,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
|
||||
// FIXME: we will want to skip the animation in some cases here to make continuously
|
||||
// resizing windows not look janky.
|
||||
self.animate_view_offset_to_column(current_x, idx, None);
|
||||
self.animate_view_offset_to_column(current_x, col_idx, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,6 +1040,16 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_for_resize_animation(&mut self, window: &W::Id) {
|
||||
let column = self
|
||||
.columns
|
||||
.iter_mut()
|
||||
.find(|col| col.contains(window))
|
||||
.unwrap();
|
||||
|
||||
column.width_before_resize = Some(column.width());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn verify_invariants(&self) {
|
||||
assert!(self.view_size.w > 0);
|
||||
@@ -1265,8 +1310,9 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
// Start with the active window since it's drawn on top.
|
||||
let col = &self.columns[self.active_column_idx];
|
||||
let tile = &col.tiles[col.active_tile_idx];
|
||||
let tile_pos =
|
||||
Point::from((-self.view_offset, col.tile_y(col.active_tile_idx))) + col.render_offset();
|
||||
let tile_pos = Point::from((-self.view_offset, col.tile_y(col.active_tile_idx)))
|
||||
+ col.render_offset()
|
||||
+ tile.render_offset();
|
||||
let first = iter::once((tile, tile_pos));
|
||||
|
||||
// Next, the rest of the tiles in the active column, since it should be drawn on top as a
|
||||
@@ -1280,7 +1326,9 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let tile_pos = Point::from((-self.view_offset, y)) + col.render_offset();
|
||||
let tile_pos = Point::from((-self.view_offset, y))
|
||||
+ col.render_offset()
|
||||
+ tile.render_offset();
|
||||
Some((tile, tile_pos))
|
||||
});
|
||||
|
||||
@@ -1305,7 +1353,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
})
|
||||
.flat_map(move |(col, x)| {
|
||||
zip(&col.tiles, col.tile_ys()).map(move |(tile, y)| {
|
||||
let tile_pos = Point::from((x, y)) + col.render_offset();
|
||||
let tile_pos = Point::from((x, y)) + col.render_offset() + tile.render_offset();
|
||||
(tile, tile_pos)
|
||||
})
|
||||
});
|
||||
@@ -1409,7 +1457,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
let window = col.tiles.remove(tile_idx).into_window();
|
||||
col.heights.remove(tile_idx);
|
||||
col.active_tile_idx = min(col.active_tile_idx, col.tiles.len() - 1);
|
||||
col.update_tile_sizes();
|
||||
col.update_tile_sizes(false);
|
||||
let width = col.width;
|
||||
let is_full_width = col.is_full_width;
|
||||
|
||||
@@ -1744,6 +1792,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
is_full_width,
|
||||
is_fullscreen: false,
|
||||
move_animation: None,
|
||||
width_before_resize: None,
|
||||
view_size,
|
||||
working_area,
|
||||
options,
|
||||
@@ -1768,7 +1817,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
self.view_size = size;
|
||||
self.working_area = working_area;
|
||||
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(false);
|
||||
}
|
||||
|
||||
fn update_config(&mut self, options: Rc<Options>) {
|
||||
@@ -1798,14 +1847,14 @@ impl<W: LayoutElement> Column<W> {
|
||||
self.options = options;
|
||||
|
||||
if update_sizes {
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(false);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_width(&mut self, width: ColumnWidth) {
|
||||
self.width = width;
|
||||
self.is_full_width = false;
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(true);
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
||||
@@ -1840,14 +1889,27 @@ impl<W: LayoutElement> Column<W> {
|
||||
}
|
||||
|
||||
pub fn animate_move_from(&mut self, from_x_offset: i32) {
|
||||
self.animate_move_from_with_config(
|
||||
from_x_offset,
|
||||
self.options.animations.window_movement,
|
||||
niri_config::Animation::default_window_movement(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn animate_move_from_with_config(
|
||||
&mut self,
|
||||
from_x_offset: i32,
|
||||
config: niri_config::Animation,
|
||||
default: niri_config::Animation,
|
||||
) {
|
||||
let current_offset = self.move_animation.as_ref().map_or(0., Animation::value);
|
||||
|
||||
self.move_animation = Some(Animation::new(
|
||||
f64::from(from_x_offset) + current_offset,
|
||||
0.,
|
||||
0.,
|
||||
self.options.animations.window_movement,
|
||||
niri_config::Animation::default_window_movement(),
|
||||
config,
|
||||
default,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1875,19 +1937,38 @@ impl<W: LayoutElement> Column<W> {
|
||||
self.is_fullscreen = false;
|
||||
self.tiles.push(tile);
|
||||
self.heights.push(WindowHeight::Auto);
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(false);
|
||||
}
|
||||
|
||||
fn update_window(&mut self, window: &W::Id) {
|
||||
let tile = self
|
||||
let (tile_idx, tile) = self
|
||||
.tiles
|
||||
.iter_mut()
|
||||
.find(|tile| tile.window().id() == window)
|
||||
.enumerate()
|
||||
.find(|(_, tile)| tile.window().id() == window)
|
||||
.unwrap();
|
||||
|
||||
let height = tile.window().size().h;
|
||||
let offset = tile
|
||||
.window()
|
||||
.animation_snapshot()
|
||||
.map_or(0, |from| from.size.h - height);
|
||||
|
||||
tile.update_window();
|
||||
|
||||
// Move windows below in tandem with resizing.
|
||||
if tile.resize_animation().is_some() && offset != 0 {
|
||||
for tile in &mut self.tiles[tile_idx + 1..] {
|
||||
tile.animate_move_from_with_config(
|
||||
Point::from((0, offset)),
|
||||
self.options.animations.window_resize,
|
||||
niri_config::Animation::default_window_resize(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_tile_sizes(&mut self) {
|
||||
fn update_tile_sizes(&mut self, animate: bool) {
|
||||
if self.is_fullscreen {
|
||||
self.tiles[0].request_fullscreen(self.view_size);
|
||||
return;
|
||||
@@ -2040,7 +2121,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
};
|
||||
|
||||
let size = Size::from((width, height));
|
||||
tile.request_tile_size(size);
|
||||
tile.request_tile_size(size, animate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2127,7 +2208,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
|
||||
fn toggle_full_width(&mut self) {
|
||||
self.is_full_width = !self.is_full_width;
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(true);
|
||||
}
|
||||
|
||||
fn set_column_width(&mut self, change: SizeChange) {
|
||||
@@ -2226,13 +2307,13 @@ impl<W: LayoutElement> Column<W> {
|
||||
}
|
||||
|
||||
self.heights[self.active_tile_idx] = WindowHeight::Fixed(window_height.clamp(1, MAX_PX));
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(true);
|
||||
}
|
||||
|
||||
fn set_fullscreen(&mut self, is_fullscreen: bool) {
|
||||
assert_eq!(self.tiles.len(), 1);
|
||||
self.is_fullscreen = is_fullscreen;
|
||||
self.update_tile_sizes();
|
||||
self.update_tile_sizes(false);
|
||||
}
|
||||
|
||||
pub fn window_y(&self, tile_idx: usize) -> i32 {
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
|
||||
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use super::primary_gpu_pixel_shader_with_textures::PrimaryGpuPixelShaderWithTexturesRenderElement;
|
||||
use super::renderer::AsGlesFrame;
|
||||
use super::shaders::Shaders;
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CrossfadeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
|
||||
|
||||
impl CrossfadeRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
renderer: &mut GlesRenderer,
|
||||
area: Rectangle<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
texture_from: (GlesTexture, Rectangle<i32, Physical>),
|
||||
size_from: Size<i32, Logical>,
|
||||
texture_to: (GlesTexture, Rectangle<i32, Physical>),
|
||||
size_to: Size<i32, Logical>,
|
||||
amount: f32,
|
||||
result_alpha: f32,
|
||||
) -> Option<Self> {
|
||||
let (texture_from, texture_from_geo) = texture_from;
|
||||
let (texture_to, texture_to_geo) = texture_to;
|
||||
|
||||
let scale_from = area.size.to_f64() / size_from.to_f64();
|
||||
let scale_to = area.size.to_f64() / size_to.to_f64();
|
||||
|
||||
let tex_from_geo = texture_from_geo.to_f64().upscale(scale_from);
|
||||
let tex_to_geo = texture_to_geo.to_f64().upscale(scale_to);
|
||||
let combined_geo = tex_from_geo.merge(tex_to_geo);
|
||||
|
||||
let area = Rectangle::from_loc_and_size(
|
||||
area.loc + combined_geo.loc.to_logical(scale).to_i32_round(),
|
||||
combined_geo.size.to_logical(scale).to_i32_round(),
|
||||
);
|
||||
|
||||
let tex_from_loc = (tex_from_geo.loc - combined_geo.loc)
|
||||
.downscale((combined_geo.size.w, combined_geo.size.h));
|
||||
let tex_to_loc = (tex_to_geo.loc - combined_geo.loc)
|
||||
.downscale((combined_geo.size.w, combined_geo.size.h));
|
||||
let tex_from_size = tex_from_geo.size / combined_geo.size;
|
||||
let tex_to_size = tex_to_geo.size / combined_geo.size;
|
||||
|
||||
// FIXME: cropping this element will mess up the coordinates.
|
||||
Shaders::get(renderer).crossfade.clone().map(|shader| {
|
||||
Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
|
||||
shader,
|
||||
HashMap::from([
|
||||
(String::from("tex_from"), texture_from),
|
||||
(String::from("tex_to"), texture_to),
|
||||
]),
|
||||
area,
|
||||
None,
|
||||
result_alpha,
|
||||
vec![
|
||||
Uniform::new(
|
||||
"tex_from_loc",
|
||||
(tex_from_loc.x as f32, tex_from_loc.y as f32),
|
||||
),
|
||||
Uniform::new(
|
||||
"tex_from_size",
|
||||
(tex_from_size.x as f32, tex_from_size.y as f32),
|
||||
),
|
||||
Uniform::new("tex_to_loc", (tex_to_loc.x as f32, tex_to_loc.y as f32)),
|
||||
Uniform::new("tex_to_size", (tex_to_size.x as f32, tex_to_size.y as f32)),
|
||||
Uniform::new("amount", amount),
|
||||
],
|
||||
Kind::Unspecified,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for CrossfadeRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.0.current_commit()
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.0.geometry(scale)
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
self.0.transform()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
self.0.src()
|
||||
}
|
||||
|
||||
fn damage_since(
|
||||
&self,
|
||||
scale: Scale<f64>,
|
||||
commit: Option<CommitCounter>,
|
||||
) -> DamageSet<i32, Physical> {
|
||||
self.0.damage_since(scale, commit)
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
|
||||
self.0.opaque_regions(scale)
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
self.0.alpha()
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
self.0.kind()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for CrossfadeRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
|
||||
self.0.underlying_storage(renderer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>> for CrossfadeRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
|
||||
self.0.underlying_storage(renderer)
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,15 @@ use smithay::wayland::shm;
|
||||
|
||||
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
|
||||
pub mod crossfade;
|
||||
pub mod gradient;
|
||||
pub mod offscreen;
|
||||
pub mod primary_gpu_pixel_shader;
|
||||
pub mod primary_gpu_pixel_shader_with_textures;
|
||||
pub mod primary_gpu_texture;
|
||||
pub mod render_elements;
|
||||
pub mod renderer;
|
||||
pub mod resources;
|
||||
pub mod shaders;
|
||||
pub mod surface;
|
||||
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::rc::Rc;
|
||||
|
||||
use glam::{Mat3, Vec2};
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{
|
||||
ffi, link_program, Capability, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
|
||||
UniformDesc, UniformName,
|
||||
};
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
|
||||
|
||||
use super::renderer::AsGlesFrame;
|
||||
use super::resources::Resources;
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
|
||||
///
|
||||
/// The shader accepts textures as input.
|
||||
#[derive(Debug)]
|
||||
pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
shader: PixelWithTexturesProgram,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
id: Id,
|
||||
commit_counter: CommitCounter,
|
||||
area: Rectangle<i32, Logical>,
|
||||
opaque_regions: Vec<Rectangle<i32, Logical>>,
|
||||
alpha: f32,
|
||||
additional_uniforms: Vec<Uniform<'static>>,
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PixelWithTexturesProgram(Rc<PixelWithTexturesProgramInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PixelWithTexturesProgramInner {
|
||||
program: ffi::types::GLuint,
|
||||
uniform_tex_matrix: ffi::types::GLint,
|
||||
uniform_matrix: ffi::types::GLint,
|
||||
uniform_size: ffi::types::GLint,
|
||||
uniform_alpha: ffi::types::GLint,
|
||||
attrib_vert: ffi::types::GLint,
|
||||
attrib_vert_position: ffi::types::GLint,
|
||||
additional_uniforms: HashMap<String, UniformDesc>,
|
||||
texture_uniforms: HashMap<String, ffi::types::GLint>,
|
||||
}
|
||||
|
||||
unsafe fn compile_program(
|
||||
gl: &ffi::Gles2,
|
||||
src: &str,
|
||||
additional_uniforms: &[UniformName<'_>],
|
||||
texture_uniforms: &[&str],
|
||||
// destruction_callback_sender: Sender<CleanupResource>,
|
||||
) -> Result<PixelWithTexturesProgram, GlesError> {
|
||||
let shader = src;
|
||||
|
||||
let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), shader)? };
|
||||
|
||||
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
|
||||
let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
|
||||
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
|
||||
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
|
||||
let size = CStr::from_bytes_with_nul(b"size\0").expect("NULL terminated");
|
||||
let alpha = CStr::from_bytes_with_nul(b"alpha\0").expect("NULL terminated");
|
||||
|
||||
Ok(PixelWithTexturesProgram(Rc::new(
|
||||
PixelWithTexturesProgramInner {
|
||||
program,
|
||||
uniform_matrix: gl
|
||||
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_tex_matrix: gl
|
||||
.GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_size: gl
|
||||
.GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_alpha: gl
|
||||
.GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert_position: gl
|
||||
.GetAttribLocation(program, vert_position.as_ptr() as *const ffi::types::GLchar),
|
||||
additional_uniforms: additional_uniforms
|
||||
.iter()
|
||||
.map(|uniform| {
|
||||
let name =
|
||||
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
||||
let location =
|
||||
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
|
||||
(
|
||||
uniform.name.clone().into_owned(),
|
||||
UniformDesc {
|
||||
location,
|
||||
type_: uniform.type_,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
texture_uniforms: texture_uniforms
|
||||
.iter()
|
||||
.map(|name_| {
|
||||
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
||||
let location =
|
||||
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
|
||||
(name_.to_string(), location)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
impl PixelWithTexturesProgram {
|
||||
pub fn compile(
|
||||
renderer: &mut GlesRenderer,
|
||||
src: &str,
|
||||
additional_uniforms: &[UniformName<'_>],
|
||||
texture_uniforms: &[&str],
|
||||
) -> Result<Self, GlesError> {
|
||||
renderer.with_context(move |gl| unsafe {
|
||||
compile_program(gl, src, additional_uniforms, texture_uniforms)
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
pub fn new(
|
||||
shader: PixelWithTexturesProgram,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
area: Rectangle<i32, Logical>,
|
||||
opaque_regions: Option<Vec<Rectangle<i32, Logical>>>,
|
||||
alpha: f32,
|
||||
additional_uniforms: Vec<Uniform<'_>>,
|
||||
kind: Kind,
|
||||
) -> Self {
|
||||
Self {
|
||||
shader,
|
||||
textures,
|
||||
id: Id::new(),
|
||||
commit_counter: CommitCounter::default(),
|
||||
area,
|
||||
opaque_regions: opaque_regions.unwrap_or_default(),
|
||||
alpha,
|
||||
additional_uniforms: additional_uniforms
|
||||
.into_iter()
|
||||
.map(|u| u.into_owned())
|
||||
.collect(),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.commit_counter
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
self.area
|
||||
.to_f64()
|
||||
.to_buffer(1.0, Transform::Normal, &self.area.size.to_f64())
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.area.to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
|
||||
self.opaque_regions
|
||||
.iter()
|
||||
.map(|region| region.to_physical_precise_round(scale))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
dest: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
let frame = frame.as_gles_frame();
|
||||
|
||||
let Some(resources) = Resources::get(frame) else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut resources = resources.borrow_mut();
|
||||
|
||||
let supports_instancing = frame.capabilities().contains(&Capability::Instancing);
|
||||
|
||||
// prepare the vertices
|
||||
resources.vertices.clear();
|
||||
if supports_instancing {
|
||||
resources.vertices.extend(damage.iter().flat_map(|rect| {
|
||||
let dest_size = dest.size;
|
||||
|
||||
let rect_constrained_loc = rect
|
||||
.loc
|
||||
.constrain(Rectangle::from_extemities((0, 0), dest_size.to_point()));
|
||||
let rect_clamped_size = rect.size.clamp(
|
||||
(0, 0),
|
||||
(dest_size.to_point() - rect_constrained_loc).to_size(),
|
||||
);
|
||||
|
||||
let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size);
|
||||
[
|
||||
rect.loc.x as f32,
|
||||
rect.loc.y as f32,
|
||||
rect.size.w as f32,
|
||||
rect.size.h as f32,
|
||||
]
|
||||
}));
|
||||
} else {
|
||||
resources.vertices.extend(damage.iter().flat_map(|rect| {
|
||||
let dest_size = dest.size;
|
||||
|
||||
let rect_constrained_loc = rect
|
||||
.loc
|
||||
.constrain(Rectangle::from_extemities((0, 0), dest_size.to_point()));
|
||||
let rect_clamped_size = rect.size.clamp(
|
||||
(0, 0),
|
||||
(dest_size.to_point() - rect_constrained_loc).to_size(),
|
||||
);
|
||||
|
||||
let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size);
|
||||
// Add the 4 f32s per damage rectangle for each of the 6 vertices.
|
||||
(0..6).flat_map(move |_| {
|
||||
[
|
||||
rect.loc.x as f32,
|
||||
rect.loc.y as f32,
|
||||
rect.size.w as f32,
|
||||
rect.size.h as f32,
|
||||
]
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
if resources.vertices.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// dest position and scale
|
||||
let mut matrix = Mat3::from_translation(Vec2::new(dest.loc.x as f32, dest.loc.y as f32));
|
||||
let tex_matrix = Mat3::from_scale(Vec2::new(
|
||||
(1.0f64 / dest.size.w as f64) as f32,
|
||||
(1.0f64 / dest.size.h as f64) as f32,
|
||||
));
|
||||
|
||||
//apply output transformation
|
||||
matrix = Mat3::from_cols_array(frame.projection()) * matrix;
|
||||
|
||||
let program = &self.shader.0;
|
||||
|
||||
// render
|
||||
frame.with_context(move |gl| -> Result<(), GlesError> {
|
||||
unsafe {
|
||||
for (i, texture) in self.textures.values().enumerate() {
|
||||
gl.ActiveTexture(ffi::TEXTURE0 + i as u32);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, texture.tex_id());
|
||||
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
|
||||
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MAG_FILTER, ffi::LINEAR as i32);
|
||||
gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_WRAP_S,
|
||||
ffi::CLAMP_TO_BORDER as i32,
|
||||
);
|
||||
gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_WRAP_T,
|
||||
ffi::CLAMP_TO_BORDER as i32,
|
||||
);
|
||||
}
|
||||
|
||||
gl.UseProgram(program.program);
|
||||
|
||||
for (i, name) in self.textures.keys().enumerate() {
|
||||
gl.Uniform1i(program.texture_uniforms[name], i as i32);
|
||||
}
|
||||
|
||||
gl.UniformMatrix3fv(
|
||||
program.uniform_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
matrix.as_ref().as_ptr(),
|
||||
);
|
||||
gl.UniformMatrix3fv(
|
||||
program.uniform_tex_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
tex_matrix.as_ref().as_ptr(),
|
||||
);
|
||||
gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32);
|
||||
gl.Uniform1f(program.uniform_alpha, self.alpha);
|
||||
|
||||
for uniform in &self.additional_uniforms {
|
||||
let desc =
|
||||
program
|
||||
.additional_uniforms
|
||||
.get(&*uniform.name)
|
||||
.ok_or_else(|| {
|
||||
GlesError::UnknownUniform(uniform.name.clone().into_owned())
|
||||
})?;
|
||||
uniform.value.set(gl, desc)?;
|
||||
}
|
||||
|
||||
gl.EnableVertexAttribArray(program.attrib_vert as u32);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[0]);
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
// vert_position
|
||||
gl.EnableVertexAttribArray(program.attrib_vert_position as u32);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[1]);
|
||||
gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
(std::mem::size_of::<ffi::types::GLfloat>() * resources.vertices.len())
|
||||
as isize,
|
||||
resources.vertices.as_ptr() as *const _,
|
||||
ffi::STREAM_DRAW,
|
||||
);
|
||||
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert_position as u32,
|
||||
4,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
let damage_len = damage.len() as i32;
|
||||
if supports_instancing {
|
||||
gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
|
||||
gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1);
|
||||
gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len);
|
||||
} else {
|
||||
// When we have more than 10 rectangles, draw them in batches of 10.
|
||||
for i in 0..(damage_len - 1) / 10 {
|
||||
gl.DrawArrays(ffi::TRIANGLES, 0, 6);
|
||||
|
||||
// Set damage pointer to the next 10 rectangles.
|
||||
let offset =
|
||||
(i + 1) as usize * 6 * 4 * std::mem::size_of::<ffi::types::GLfloat>();
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert_position as u32,
|
||||
4,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
offset as *const _,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the up to 10 remaining rectangles.
|
||||
let count = ((damage_len - 1) % 10 + 1) * 6;
|
||||
gl.DrawArrays(ffi::TRIANGLES, 0, count);
|
||||
}
|
||||
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||
gl.ActiveTexture(ffi::TEXTURE0);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||
gl.DisableVertexAttribArray(program.attrib_vert as u32);
|
||||
gl.DisableVertexAttribArray(program.attrib_vert_position as u32);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
|
||||
// If scanout for things other than Wayland buffers is implemented, this will need to take
|
||||
// the target GPU into account.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>>
|
||||
for PrimaryGpuPixelShaderWithTexturesRenderElement
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let frame = frame.as_gles_frame();
|
||||
|
||||
RenderElement::<GlesRenderer>::draw(self, frame, src, dst, damage)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&self,
|
||||
_renderer: &mut TtyRenderer<'render>,
|
||||
) -> Option<UnderlyingStorage> {
|
||||
// If scanout for things other than Wayland buffers is implemented, this will need to take
|
||||
// the target GPU into account.
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use smithay::backend::renderer::gles::{ffi, Capability, GlesError, GlesFrame, GlesRenderer};
|
||||
|
||||
pub struct Resources {
|
||||
pub vertices: Vec<f32>,
|
||||
pub vbos: [ffi::types::GLuint; 2],
|
||||
}
|
||||
|
||||
static INSTANCED_VERTS: [ffi::types::GLfloat; 8] = [
|
||||
1.0, 0.0, // top right
|
||||
0.0, 0.0, // top left
|
||||
1.0, 1.0, // bottom right
|
||||
0.0, 1.0, // bottom left
|
||||
];
|
||||
|
||||
/// Vertices for rendering individual triangles.
|
||||
const MAX_RECTS_PER_DRAW: usize = 10;
|
||||
const TRIANGLE_VERTS: [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] = triangle_verts();
|
||||
const fn triangle_verts() -> [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] {
|
||||
let mut verts = [0.; 12 * MAX_RECTS_PER_DRAW];
|
||||
let mut i = 0;
|
||||
loop {
|
||||
// Top Left.
|
||||
verts[i * 12] = 0.0;
|
||||
verts[i * 12 + 1] = 0.0;
|
||||
|
||||
// Bottom left.
|
||||
verts[i * 12 + 2] = 0.0;
|
||||
verts[i * 12 + 3] = 1.0;
|
||||
|
||||
// Bottom right.
|
||||
verts[i * 12 + 4] = 1.0;
|
||||
verts[i * 12 + 5] = 1.0;
|
||||
|
||||
// Top left.
|
||||
verts[i * 12 + 6] = 0.0;
|
||||
verts[i * 12 + 7] = 0.0;
|
||||
|
||||
// Bottom right.
|
||||
verts[i * 12 + 8] = 1.0;
|
||||
verts[i * 12 + 9] = 1.0;
|
||||
|
||||
// Top right.
|
||||
verts[i * 12 + 10] = 1.0;
|
||||
verts[i * 12 + 11] = 0.0;
|
||||
|
||||
i += 1;
|
||||
if i == MAX_RECTS_PER_DRAW {
|
||||
break;
|
||||
}
|
||||
}
|
||||
verts
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
fn create(renderer: &mut GlesRenderer) -> Result<Self, GlesError> {
|
||||
let _span = tracy_client::span!("Resources::init");
|
||||
|
||||
let supports_instancing = renderer.capabilities().contains(&Capability::Instancing);
|
||||
renderer.with_context(|gl| unsafe {
|
||||
let vertices: &[ffi::types::GLfloat] = if supports_instancing {
|
||||
&INSTANCED_VERTS
|
||||
} else {
|
||||
&TRIANGLE_VERTS
|
||||
};
|
||||
|
||||
let mut vbos = [0; 2];
|
||||
gl.GenBuffers(vbos.len() as i32, vbos.as_mut_ptr());
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
|
||||
gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
std::mem::size_of_val(vertices) as isize,
|
||||
vertices.as_ptr() as *const _,
|
||||
ffi::STATIC_DRAW,
|
||||
);
|
||||
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
|
||||
Self {
|
||||
vertices: vec![],
|
||||
vbos,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(frame: &mut GlesFrame) -> Option<Rc<RefCell<Self>>> {
|
||||
let data = frame.egl_context().user_data();
|
||||
data.get().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(renderer: &mut GlesRenderer) {
|
||||
match Resources::create(renderer) {
|
||||
Ok(resources) => {
|
||||
let data = renderer.egl_context().user_data();
|
||||
if !data.insert_if_missing(|| Rc::new(RefCell::new(resources))) {
|
||||
error!("resources were already initialized");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("error creating resources for rendering: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D tex_from;
|
||||
uniform vec2 tex_from_loc;
|
||||
uniform vec2 tex_from_size;
|
||||
|
||||
uniform sampler2D tex_to;
|
||||
uniform vec2 tex_to_loc;
|
||||
uniform vec2 tex_to_size;
|
||||
|
||||
uniform float alpha;
|
||||
uniform float amount;
|
||||
|
||||
uniform vec2 size;
|
||||
varying vec2 v_coords;
|
||||
|
||||
void main() {
|
||||
vec2 coords_from = (v_coords - tex_from_loc) / tex_from_size;
|
||||
vec2 coords_to = (v_coords - tex_to_loc) / tex_to_size;
|
||||
|
||||
vec4 color_from = texture2D(tex_from, coords_from);
|
||||
vec4 color_to = texture2D(tex_to, coords_to);
|
||||
|
||||
vec4 color = mix(color_from, color_to, amount);
|
||||
color = color * alpha;
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use smithay::backend::renderer::gles::{GlesPixelProgram, GlesRenderer, UniformName, UniformType};
|
||||
|
||||
use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
|
||||
use super::renderer::NiriRenderer;
|
||||
|
||||
pub struct Shaders {
|
||||
pub gradient_border: Option<GlesPixelProgram>,
|
||||
pub crossfade: Option<PixelWithTexturesProgram>,
|
||||
}
|
||||
|
||||
impl Shaders {
|
||||
@@ -26,7 +28,27 @@ impl Shaders {
|
||||
})
|
||||
.ok();
|
||||
|
||||
Self { gradient_border }
|
||||
let crossfade = PixelWithTexturesProgram::compile(
|
||||
renderer,
|
||||
include_str!("crossfade.frag"),
|
||||
&[
|
||||
UniformName::new("tex_from_loc", UniformType::_2f),
|
||||
UniformName::new("tex_from_size", UniformType::_2f),
|
||||
UniformName::new("tex_to_loc", UniformType::_2f),
|
||||
UniformName::new("tex_to_size", UniformType::_2f),
|
||||
UniformName::new("amount", UniformType::_1f),
|
||||
],
|
||||
&["tex_from", "tex_to"],
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("error compiling crossfade shader: {err:?}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
Self {
|
||||
gradient_border,
|
||||
crossfade,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(renderer: &mut impl NiriRenderer) -> &Self {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#version 100
|
||||
|
||||
uniform mat3 matrix;
|
||||
uniform mat3 tex_matrix;
|
||||
|
||||
attribute vec2 vert;
|
||||
attribute vec4 vert_position;
|
||||
|
||||
varying vec2 v_coords;
|
||||
|
||||
mat2 scale(vec2 scale_vec){
|
||||
return mat2(
|
||||
scale_vec.x, 0.0,
|
||||
0.0, scale_vec.y
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 vert_transform_translation = vert_position.xy;
|
||||
vec2 vert_transform_scale = vert_position.zw;
|
||||
vec3 position = vec3(vert * scale(vert_transform_scale) + vert_transform_translation, 1.0);
|
||||
v_coords = (tex_matrix * position).xy;
|
||||
gl_Position = vec4(matrix * position, 1.0);
|
||||
}
|
||||
|
||||
+77
-14
@@ -11,14 +11,16 @@ use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
||||
use smithay::wayland::compositor::{
|
||||
remove_pre_commit_hook, send_surface_state, with_states, HookId,
|
||||
};
|
||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
|
||||
|
||||
use super::{ResolvedWindowRules, WindowRef};
|
||||
use crate::layout::{LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot};
|
||||
use crate::layout::{
|
||||
AnimationSnapshot, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
|
||||
};
|
||||
use crate::niri::WindowOffscreenId;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::surface::render_snapshot_from_surface_tree;
|
||||
@@ -48,6 +50,15 @@ pub struct Mapped {
|
||||
|
||||
/// Snapshot of the last render for use in the close animation.
|
||||
last_render: RefCell<LayoutElementRenderSnapshot>,
|
||||
|
||||
/// Whether the next configure should be animated, if the configured state changed.
|
||||
animate_next_configure: bool,
|
||||
|
||||
/// Serials of commits that should be animated.
|
||||
animate_serials: Vec<Serial>,
|
||||
|
||||
/// Snapshot right before an animated commit.
|
||||
animation_snapshot: Option<AnimationSnapshot>,
|
||||
}
|
||||
|
||||
impl Mapped {
|
||||
@@ -60,6 +71,9 @@ impl Mapped {
|
||||
is_focused: false,
|
||||
block_out_buffer: RefCell::new(SolidColorBuffer::new((0, 0), [0., 0., 0., 1.])),
|
||||
last_render: RefCell::new(RenderSnapshot::default()),
|
||||
animate_next_configure: false,
|
||||
animate_serials: Vec::new(),
|
||||
animation_snapshot: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,15 +115,13 @@ impl Mapped {
|
||||
self.need_to_recompute_rules = true;
|
||||
}
|
||||
|
||||
pub fn render_and_store_snapshot(&self, renderer: &mut GlesRenderer) {
|
||||
let mut snapshot = self.last_render.borrow_mut();
|
||||
if !snapshot.contents.is_empty() {
|
||||
return;
|
||||
}
|
||||
fn render_snapshot(&self, renderer: &mut GlesRenderer) -> LayoutElementRenderSnapshot {
|
||||
let _span = tracy_client::span!("Mapped::render_snapshot");
|
||||
|
||||
snapshot.contents.clear();
|
||||
snapshot.blocked_out_contents.clear();
|
||||
snapshot.block_out_from = self.rules.block_out_from;
|
||||
let mut snapshot = RenderSnapshot {
|
||||
block_out_from: self.rules.block_out_from,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut buffer = self.block_out_buffer.borrow_mut();
|
||||
buffer.resize(self.window.geometry().size);
|
||||
@@ -135,6 +147,37 @@ impl Mapped {
|
||||
}
|
||||
|
||||
render_snapshot_from_surface_tree(renderer, surface, buf_pos, &mut snapshot.contents);
|
||||
|
||||
snapshot
|
||||
}
|
||||
|
||||
pub fn render_and_store_snapshot(&self, renderer: &mut GlesRenderer) {
|
||||
let mut snapshot = self.last_render.borrow_mut();
|
||||
if !snapshot.contents.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
*snapshot = self.render_snapshot(renderer);
|
||||
}
|
||||
|
||||
pub fn should_animate_commit(&mut self, commit_serial: Serial) -> bool {
|
||||
let mut should_animate = false;
|
||||
self.animate_serials.retain_mut(|serial| {
|
||||
if commit_serial.is_no_older_than(serial) {
|
||||
should_animate = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
should_animate
|
||||
}
|
||||
|
||||
pub fn store_animation_snapshot(&mut self, renderer: &mut GlesRenderer) {
|
||||
self.animation_snapshot = Some(AnimationSnapshot {
|
||||
render: self.render_snapshot(renderer),
|
||||
size: self.size(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,11 +243,17 @@ impl LayoutElement for Mapped {
|
||||
self.last_render.take()
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
self.toplevel().with_pending_state(|state| {
|
||||
fn request_size(&mut self, size: Size<i32, Logical>, animate: bool) {
|
||||
let changed = self.toplevel().with_pending_state(|state| {
|
||||
let changed = state.size != Some(size);
|
||||
state.size = Some(size);
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
changed
|
||||
});
|
||||
|
||||
if changed && animate {
|
||||
self.animate_next_configure = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn request_fullscreen(&self, size: Size<i32, Logical>) {
|
||||
@@ -303,8 +352,14 @@ impl LayoutElement for Mapped {
|
||||
});
|
||||
}
|
||||
|
||||
fn send_pending_configure(&self) {
|
||||
self.toplevel().send_pending_configure();
|
||||
fn send_pending_configure(&mut self) {
|
||||
if let Some(serial) = self.toplevel().send_pending_configure() {
|
||||
if self.animate_next_configure {
|
||||
self.animate_serials.push(serial);
|
||||
}
|
||||
}
|
||||
|
||||
self.animate_next_configure = false;
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
@@ -326,4 +381,12 @@ impl LayoutElement for Mapped {
|
||||
fn rules(&self) -> &ResolvedWindowRules {
|
||||
&self.rules
|
||||
}
|
||||
|
||||
fn animation_snapshot(&self) -> Option<&AnimationSnapshot> {
|
||||
self.animation_snapshot.as_ref()
|
||||
}
|
||||
|
||||
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot> {
|
||||
self.animation_snapshot.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ animations {
|
||||
curve "ease-out-quad"
|
||||
}
|
||||
|
||||
window-resize {
|
||||
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
|
||||
}
|
||||
|
||||
config-notification-open-close {
|
||||
spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
|
||||
}
|
||||
@@ -196,6 +200,23 @@ animations {
|
||||
}
|
||||
```
|
||||
|
||||
#### `window-resize`
|
||||
|
||||
<sup>Since: 0.1.5</sup>
|
||||
|
||||
Window resize animation.
|
||||
|
||||
Only manual window resizes are animated, i.e. when you resize the window with `switch-preset-column-width` or `maximize-column`.
|
||||
Also, very small resizes (up to 10 pixels) are not animated.
|
||||
|
||||
```
|
||||
animations {
|
||||
window-resize {
|
||||
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `config-notification-open-close`
|
||||
|
||||
The open/close animation of the config parse error and new default config notifications.
|
||||
|
||||
Reference in New Issue
Block a user