mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Add niri-visual-tests
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "niri-visual-tests"
|
||||
version.workspace = true
|
||||
description.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] }
|
||||
anyhow.workspace = true
|
||||
gtk = { version = "0.8.0", package = "gtk4", features = ["v4_12"] }
|
||||
niri = { version = "0.1.1", path = ".." }
|
||||
niri-config = { version = "0.1.1", path = "../niri-config" }
|
||||
smithay.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
@@ -0,0 +1,14 @@
|
||||
# niri-visual-tests
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This is a development-only app, you shouldn't package it.
|
||||
|
||||
This app contains a number of hard-coded test scenarios for visual inspection.
|
||||
It uses the real niri layout and rendering code, but with mock windows instead of Wayland clients.
|
||||
The idea is to go through the test scenarios and check that everything *looks* right.
|
||||
|
||||
## Running
|
||||
|
||||
You will need recent GTK and libadwaita.
|
||||
Then, `cargo run`.
|
||||
@@ -0,0 +1,3 @@
|
||||
.anim-control-bar {
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Physical, Size};
|
||||
|
||||
pub mod tile;
|
||||
pub mod window;
|
||||
|
||||
pub trait TestCase {
|
||||
fn resize(&mut self, width: i32, height: i32);
|
||||
fn are_animations_ongoing(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn advance_animations(&mut self, _current_time: Duration) {}
|
||||
fn render(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>>;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri::layout::tile::Tile;
|
||||
use niri::layout::Options;
|
||||
use niri_config::Color;
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Physical, Point, Scale, Size};
|
||||
|
||||
use super::TestCase;
|
||||
use crate::test_window::TestWindow;
|
||||
|
||||
pub struct JustTile {
|
||||
window: TestWindow,
|
||||
tile: Tile<TestWindow>,
|
||||
}
|
||||
|
||||
impl JustTile {
|
||||
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.window.communicate();
|
||||
rv
|
||||
}
|
||||
|
||||
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.window.communicate();
|
||||
rv
|
||||
}
|
||||
|
||||
pub fn fixed_size_with_csd_shadow(size: Size<i32, Logical>) -> Self {
|
||||
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.window.communicate();
|
||||
rv
|
||||
}
|
||||
|
||||
pub fn with_window(window: TestWindow) -> Self {
|
||||
let options = Options {
|
||||
border: niri_config::FocusRing {
|
||||
off: false,
|
||||
width: 32,
|
||||
active_color: Color::new(255, 163, 72, 255),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let tile = Tile::new(window.clone(), Rc::new(options));
|
||||
Self { window, tile }
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCase for JustTile {
|
||||
fn resize(&mut self, width: i32, height: i32) {
|
||||
self.tile.request_tile_size(Size::from((width, height)));
|
||||
self.window.communicate();
|
||||
}
|
||||
|
||||
fn advance_animations(&mut self, current_time: Duration) {
|
||||
self.tile.advance_animations(current_time, true);
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||
let tile_size = self.tile.tile_size().to_physical(1);
|
||||
let location = Point::from(((size.w - tile_size.w) / 2, (size.h - tile_size.h) / 2));
|
||||
|
||||
self.tile
|
||||
.render(renderer, location, Scale::from(1.))
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use niri::layout::LayoutElement;
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Physical, Point, Scale, Size};
|
||||
|
||||
use super::TestCase;
|
||||
use crate::test_window::TestWindow;
|
||||
|
||||
pub struct JustWindow {
|
||||
window: TestWindow,
|
||||
}
|
||||
|
||||
impl JustWindow {
|
||||
pub fn freeform(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::freeform(0);
|
||||
window.request_size(size);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn fixed_size(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
window.request_size(size);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn fixed_size_with_csd_shadow(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
window.set_csd_shadow_width(64);
|
||||
window.request_size(size);
|
||||
window.communicate();
|
||||
Self { window }
|
||||
}
|
||||
}
|
||||
|
||||
impl TestCase for JustWindow {
|
||||
fn resize(&mut self, width: i32, height: i32) {
|
||||
self.window.request_size(Size::from((width, height)));
|
||||
self.window.communicate();
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||
let win_size = self.window.size().to_physical(1);
|
||||
let location = Point::from(((size.w - win_size.w) / 2, (size.h - win_size.h) / 2));
|
||||
|
||||
self.window
|
||||
.render(renderer, location, Scale::from(1.))
|
||||
.into_iter()
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
use std::env;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt};
|
||||
use cases::tile::JustTile;
|
||||
use cases::window::JustWindow;
|
||||
use gtk::prelude::{
|
||||
AdjustmentExt, ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt,
|
||||
};
|
||||
use gtk::{gdk, gio, glib};
|
||||
use niri::animation::ANIMATION_SLOWDOWN;
|
||||
use smithay::utils::{Logical, Size};
|
||||
use smithay_view::SmithayView;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::cases::TestCase;
|
||||
|
||||
mod cases;
|
||||
mod smithay_view;
|
||||
mod test_window;
|
||||
|
||||
fn main() -> glib::ExitCode {
|
||||
let directives =
|
||||
env::var("RUST_LOG").unwrap_or_else(|_| "niri-visual-tests=debug,niri=debug".to_owned());
|
||||
let env_filter = EnvFilter::builder().parse_lossy(directives);
|
||||
tracing_subscriber::fmt()
|
||||
.compact()
|
||||
.with_env_filter(env_filter)
|
||||
.init();
|
||||
|
||||
let app = adw::Application::new(None::<&str>, gio::ApplicationFlags::NON_UNIQUE);
|
||||
app.connect_startup(on_startup);
|
||||
app.connect_activate(build_ui);
|
||||
app.run()
|
||||
}
|
||||
|
||||
fn on_startup(_app: &adw::Application) {
|
||||
// Load our CSS.
|
||||
let provider = gtk::CssProvider::new();
|
||||
provider.load_from_string(include_str!("../resources/style.css"));
|
||||
if let Some(display) = gdk::Display::default() {
|
||||
gtk::style_context_add_provider_for_display(
|
||||
&display,
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_ui(app: &adw::Application) {
|
||||
let stack = gtk::Stack::new();
|
||||
|
||||
struct S {
|
||||
stack: gtk::Stack,
|
||||
}
|
||||
|
||||
impl S {
|
||||
fn add<T: TestCase + 'static>(
|
||||
&self,
|
||||
make: impl Fn(Size<i32, Logical>) -> T + 'static,
|
||||
title: &str,
|
||||
) {
|
||||
let view = SmithayView::new(make);
|
||||
self.stack.add_titled(&view, None, title);
|
||||
}
|
||||
}
|
||||
|
||||
let s = S {
|
||||
stack: stack.clone(),
|
||||
};
|
||||
|
||||
s.add(JustWindow::freeform, "Freeform Window");
|
||||
s.add(JustWindow::fixed_size, "Fixed Size Window");
|
||||
s.add(
|
||||
JustWindow::fixed_size_with_csd_shadow,
|
||||
"Fixed Size Window - CSD Shadow",
|
||||
);
|
||||
s.add(JustTile::freeform, "Freeform Tile");
|
||||
s.add(JustTile::fixed_size, "Fixed Size Tile");
|
||||
s.add(
|
||||
JustTile::fixed_size_with_csd_shadow,
|
||||
"Fixed Size Tile - CSD Shadow",
|
||||
);
|
||||
|
||||
let content_headerbar = adw::HeaderBar::new();
|
||||
|
||||
let anim_adjustment = gtk::Adjustment::new(1., 0., 10., 0.1, 0.5, 0.);
|
||||
anim_adjustment
|
||||
.connect_value_changed(|adj| ANIMATION_SLOWDOWN.store(adj.value(), Ordering::SeqCst));
|
||||
let anim_scale = gtk::Scale::new(gtk::Orientation::Horizontal, Some(&anim_adjustment));
|
||||
anim_scale.set_hexpand(true);
|
||||
|
||||
let anim_control_bar = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
anim_control_bar.add_css_class("anim-control-bar");
|
||||
anim_control_bar.append(>k::Label::new(Some("Slowdown")));
|
||||
anim_control_bar.append(&anim_scale);
|
||||
|
||||
let content_view = adw::ToolbarView::new();
|
||||
content_view.set_top_bar_style(adw::ToolbarStyle::RaisedBorder);
|
||||
content_view.set_bottom_bar_style(adw::ToolbarStyle::RaisedBorder);
|
||||
content_view.add_top_bar(&content_headerbar);
|
||||
content_view.add_bottom_bar(&anim_control_bar);
|
||||
content_view.set_content(Some(&stack));
|
||||
let content = adw::NavigationPage::new(
|
||||
&content_view,
|
||||
stack
|
||||
.page(&stack.visible_child().unwrap())
|
||||
.title()
|
||||
.as_deref()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let sidebar_header = adw::HeaderBar::new();
|
||||
let stack_sidebar = gtk::StackSidebar::new();
|
||||
stack_sidebar.set_stack(&stack);
|
||||
let sidebar_view = adw::ToolbarView::new();
|
||||
sidebar_view.add_top_bar(&sidebar_header);
|
||||
sidebar_view.set_content(Some(&stack_sidebar));
|
||||
let sidebar = adw::NavigationPage::new(&sidebar_view, "Tests");
|
||||
|
||||
let split_view = adw::NavigationSplitView::new();
|
||||
split_view.set_content(Some(&content));
|
||||
split_view.set_sidebar(Some(&sidebar));
|
||||
|
||||
stack.connect_visible_child_notify(move |stack| {
|
||||
content.set_title(
|
||||
stack
|
||||
.visible_child()
|
||||
.and_then(|c| stack.page(&c).title())
|
||||
.as_deref()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
|
||||
let window = adw::ApplicationWindow::new(app);
|
||||
window.set_title(Some("niri visual tests"));
|
||||
window.set_content(Some(&split_view));
|
||||
window.present();
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
use gtk::glib;
|
||||
use gtk::subclass::prelude::*;
|
||||
use smithay::utils::{Logical, Size};
|
||||
|
||||
use crate::cases::TestCase;
|
||||
|
||||
mod imp {
|
||||
use std::cell::{Cell, OnceCell, RefCell};
|
||||
use std::ptr::null;
|
||||
|
||||
use anyhow::{ensure, Context};
|
||||
use gtk::gdk;
|
||||
use gtk::prelude::*;
|
||||
use niri::utils::get_monotonic_time;
|
||||
use smithay::backend::egl::ffi::egl;
|
||||
use smithay::backend::egl::EGLContext;
|
||||
use smithay::backend::renderer::gles::{Capability, GlesRenderer};
|
||||
use smithay::backend::renderer::{Frame, Renderer, Unbind};
|
||||
use smithay::utils::{Physical, Rectangle, Scale, Transform};
|
||||
|
||||
use super::*;
|
||||
|
||||
type DynMakeTestCase = Box<dyn Fn(Size<i32, Logical>) -> Box<dyn TestCase>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SmithayView {
|
||||
gl_area: gtk::GLArea,
|
||||
size: Cell<(i32, i32)>,
|
||||
renderer: RefCell<Option<Result<GlesRenderer, ()>>>,
|
||||
pub make_test_case: OnceCell<DynMakeTestCase>,
|
||||
test_case: RefCell<Option<Box<dyn TestCase>>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for SmithayView {
|
||||
const NAME: &'static str = "NiriSmithayView";
|
||||
type Type = super::SmithayView;
|
||||
type ParentType = gtk::Widget;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_layout_manager_type::<gtk::BinLayout>();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for SmithayView {
|
||||
fn constructed(&self) {
|
||||
let obj = self.obj();
|
||||
|
||||
self.parent_constructed();
|
||||
|
||||
self.gl_area.set_allowed_apis(gdk::GLAPI::GLES);
|
||||
self.gl_area.set_parent(&*obj);
|
||||
|
||||
self.gl_area.connect_resize({
|
||||
let imp = self.downgrade();
|
||||
move |_, width, height| {
|
||||
if let Some(imp) = imp.upgrade() {
|
||||
imp.resize(width, height);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.gl_area.connect_render({
|
||||
let imp = self.downgrade();
|
||||
move |_, gl_context| {
|
||||
if let Some(imp) = imp.upgrade() {
|
||||
if let Err(err) = imp.render(gl_context) {
|
||||
warn!("error rendering: {err:?}");
|
||||
}
|
||||
}
|
||||
glib::Propagation::Stop
|
||||
}
|
||||
});
|
||||
|
||||
obj.add_tick_callback(|obj, _frame_clock| {
|
||||
let imp = obj.imp();
|
||||
|
||||
if let Some(case) = &mut *imp.test_case.borrow_mut() {
|
||||
if case.are_animations_ongoing() {
|
||||
imp.gl_area.queue_draw();
|
||||
}
|
||||
}
|
||||
|
||||
glib::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
self.gl_area.unparent();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for SmithayView {
|
||||
fn unmap(&self) {
|
||||
self.test_case.replace(None);
|
||||
self.parent_unmap();
|
||||
}
|
||||
|
||||
fn unrealize(&self) {
|
||||
self.renderer.replace(None);
|
||||
self.parent_unrealize();
|
||||
}
|
||||
}
|
||||
|
||||
impl SmithayView {
|
||||
fn resize(&self, width: i32, height: i32) {
|
||||
self.size.set((width, height));
|
||||
|
||||
if let Some(case) = &mut *self.test_case.borrow_mut() {
|
||||
case.resize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, _gl_context: &gdk::GLContext) -> anyhow::Result<()> {
|
||||
// Set up the Smithay renderer.
|
||||
let mut renderer = self.renderer.borrow_mut();
|
||||
let renderer = renderer.get_or_insert_with(|| {
|
||||
unsafe { create_renderer() }
|
||||
.map_err(|err| warn!("error creating a Smithay renderer: {err:?}"))
|
||||
});
|
||||
let Ok(renderer) = renderer else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let size = self.size.get();
|
||||
|
||||
// Create the test case if missing.
|
||||
let mut case = self.test_case.borrow_mut();
|
||||
let case = case.get_or_insert_with(|| {
|
||||
let make = self.make_test_case.get().unwrap();
|
||||
make(Size::from(size))
|
||||
});
|
||||
|
||||
case.advance_animations(get_monotonic_time());
|
||||
|
||||
let rect: Rectangle<i32, Physical> = Rectangle::from_loc_and_size((0, 0), size);
|
||||
|
||||
let elements = unsafe {
|
||||
with_framebuffer_save_restore(renderer, |renderer| {
|
||||
case.render(renderer, Size::from(size))
|
||||
})
|
||||
}?;
|
||||
|
||||
let mut frame = renderer
|
||||
.render(rect.size, Transform::Normal)
|
||||
.context("error creating frame")?;
|
||||
|
||||
frame
|
||||
.clear([0.3, 0.3, 0.3, 1.], &[rect])
|
||||
.context("error clearing")?;
|
||||
|
||||
for element in elements.iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(Scale::from(1.));
|
||||
|
||||
if let Some(mut damage) = rect.intersection(dst) {
|
||||
damage.loc -= dst.loc;
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[damage])
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_renderer() -> anyhow::Result<GlesRenderer> {
|
||||
smithay::backend::egl::ffi::make_sure_egl_is_loaded()
|
||||
.context("error loading EGL symbols in Smithay")?;
|
||||
|
||||
let egl_display = egl::GetCurrentDisplay();
|
||||
ensure!(egl_display != egl::NO_DISPLAY, "no current EGL display");
|
||||
|
||||
let egl_context = egl::GetCurrentContext();
|
||||
ensure!(egl_context != egl::NO_CONTEXT, "no current EGL context");
|
||||
|
||||
// There's no config ID on the EGL context and there's no current EGL surface, but we don't
|
||||
// really use it anyway so just get some random one.
|
||||
let mut egl_config_id = null();
|
||||
let mut num_configs = 0;
|
||||
let res = egl::GetConfigs(egl_display, &mut egl_config_id, 1, &mut num_configs);
|
||||
ensure!(res == egl::TRUE, "error choosing EGL config");
|
||||
ensure!(num_configs != 0, "no EGL config");
|
||||
|
||||
let egl_context = EGLContext::from_raw(egl_display, egl_config_id as *const _, egl_context)
|
||||
.context("error creating EGL context")?;
|
||||
let capabilities = GlesRenderer::supported_capabilities(&egl_context)
|
||||
.context("error getting supported renderer capabilities")?
|
||||
.into_iter()
|
||||
.filter(|c| *c != Capability::ColorTransformations);
|
||||
|
||||
GlesRenderer::with_capabilities(egl_context, capabilities)
|
||||
.context("error creating GlesRenderer")
|
||||
}
|
||||
|
||||
unsafe fn with_framebuffer_save_restore<T>(
|
||||
renderer: &mut GlesRenderer,
|
||||
f: impl FnOnce(&mut GlesRenderer) -> T,
|
||||
) -> anyhow::Result<T> {
|
||||
let mut framebuffer = 0;
|
||||
renderer
|
||||
.with_context(|gl| unsafe {
|
||||
gl.GetIntegerv(
|
||||
smithay::backend::renderer::gles::ffi::FRAMEBUFFER_BINDING,
|
||||
&mut framebuffer,
|
||||
);
|
||||
})
|
||||
.context("error running closure in GL context")?;
|
||||
ensure!(framebuffer != 0, "error getting the framebuffer");
|
||||
|
||||
let rv = f(renderer);
|
||||
|
||||
renderer.unbind().context("error unbinding")?;
|
||||
renderer
|
||||
.with_context(|gl| unsafe {
|
||||
gl.BindFramebuffer(
|
||||
smithay::backend::renderer::gles::ffi::FRAMEBUFFER,
|
||||
framebuffer as u32,
|
||||
);
|
||||
})
|
||||
.context("error running closure in GL context")?;
|
||||
|
||||
Ok(rv)
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SmithayView(ObjectSubclass<imp::SmithayView>)
|
||||
@extends gtk::Widget;
|
||||
}
|
||||
|
||||
impl SmithayView {
|
||||
pub fn new<T: TestCase + 'static>(
|
||||
make_test_case: impl Fn(Size<i32, Logical>) -> T + 'static,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::builder().build();
|
||||
|
||||
let make = move |size| Box::new(make_test_case(size)) as Box<dyn TestCase>;
|
||||
let make_test_case = Box::new(make) as _;
|
||||
let _ = obj.imp().make_test_case.set(make_test_case);
|
||||
|
||||
obj
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::{max, min};
|
||||
use std::rc::Rc;
|
||||
|
||||
use niri::layout::{LayoutElement, LayoutElementRenderElement};
|
||||
use niri::render_helpers::NiriRenderer;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Scale, Size, Transform};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestWindowInner {
|
||||
id: usize,
|
||||
size: Size<i32, Logical>,
|
||||
requested_size: Option<Size<i32, Logical>>,
|
||||
min_size: Size<i32, Logical>,
|
||||
max_size: Size<i32, Logical>,
|
||||
buffer: SolidColorBuffer,
|
||||
pending_fullscreen: bool,
|
||||
csd_shadow_width: i32,
|
||||
csd_shadow_buffer: SolidColorBuffer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestWindow(Rc<RefCell<TestWindowInner>>);
|
||||
|
||||
impl TestWindow {
|
||||
pub fn freeform(id: usize) -> Self {
|
||||
let size = Size::from((100, 200));
|
||||
let min_size = Size::from((0, 0));
|
||||
let max_size = Size::from((0, 0));
|
||||
let buffer = SolidColorBuffer::new(size, [0.15, 0.64, 0.41, 1.]);
|
||||
|
||||
Self(Rc::new(RefCell::new(TestWindowInner {
|
||||
id,
|
||||
size,
|
||||
requested_size: None,
|
||||
min_size,
|
||||
max_size,
|
||||
buffer,
|
||||
pending_fullscreen: false,
|
||||
csd_shadow_width: 0,
|
||||
csd_shadow_buffer: SolidColorBuffer::new((0, 0), [0., 0., 0., 0.3]),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn fixed_size(id: usize) -> Self {
|
||||
let rv = Self::freeform(id);
|
||||
rv.set_min_size((200, 400).into());
|
||||
rv.set_max_size((200, 400).into());
|
||||
rv.set_color([0.88, 0.11, 0.14, 1.]);
|
||||
rv.communicate();
|
||||
rv
|
||||
}
|
||||
|
||||
pub fn set_min_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().min_size = size;
|
||||
}
|
||||
|
||||
pub fn set_max_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().max_size = size;
|
||||
}
|
||||
|
||||
pub fn set_color(&self, color: [f32; 4]) {
|
||||
self.0.borrow_mut().buffer.set_color(color);
|
||||
}
|
||||
|
||||
pub fn set_csd_shadow_width(&self, width: i32) {
|
||||
self.0.borrow_mut().csd_shadow_width = width;
|
||||
}
|
||||
|
||||
pub fn communicate(&self) -> bool {
|
||||
let mut rv = false;
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
let mut new_size = inner.size;
|
||||
|
||||
if let Some(size) = inner.requested_size.take() {
|
||||
assert!(size.w >= 0);
|
||||
assert!(size.h >= 0);
|
||||
|
||||
if size.w != 0 {
|
||||
new_size.w = size.w;
|
||||
}
|
||||
if size.h != 0 {
|
||||
new_size.h = size.h;
|
||||
}
|
||||
}
|
||||
|
||||
if inner.max_size.w > 0 {
|
||||
new_size.w = min(new_size.w, inner.max_size.w);
|
||||
}
|
||||
if inner.max_size.h > 0 {
|
||||
new_size.h = min(new_size.h, inner.max_size.h);
|
||||
}
|
||||
if inner.min_size.w > 0 {
|
||||
new_size.w = max(new_size.w, inner.min_size.w);
|
||||
}
|
||||
if inner.min_size.h > 0 {
|
||||
new_size.h = max(new_size.h, inner.min_size.h);
|
||||
}
|
||||
|
||||
if inner.size != new_size {
|
||||
inner.size = new_size;
|
||||
inner.buffer.resize(new_size);
|
||||
rv = true;
|
||||
}
|
||||
|
||||
let mut csd_shadow_size = new_size;
|
||||
csd_shadow_size.w += inner.csd_shadow_width * 2;
|
||||
csd_shadow_size.h += inner.csd_shadow_width * 2;
|
||||
inner.csd_shadow_buffer.resize(csd_shadow_size);
|
||||
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TestWindow {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.borrow().id == other.0.borrow().id
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutElement for TestWindow {
|
||||
fn size(&self) -> Size<i32, Logical> {
|
||||
self.0.borrow().size
|
||||
}
|
||||
|
||||
fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
fn is_in_input_region(&self, _point: Point<f64, Logical>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
) -> Vec<LayoutElementRenderElement<R>> {
|
||||
let inner = self.0.borrow();
|
||||
|
||||
vec![
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.buffer,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.csd_shadow_buffer,
|
||||
(location - Point::from((inner.csd_shadow_width, inner.csd_shadow_width)))
|
||||
.to_physical_precise_round(scale),
|
||||
scale,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().requested_size = Some(size);
|
||||
self.0.borrow_mut().pending_fullscreen = false;
|
||||
}
|
||||
|
||||
fn request_fullscreen(&self, _size: Size<i32, Logical>) {
|
||||
self.0.borrow_mut().pending_fullscreen = true;
|
||||
}
|
||||
|
||||
fn min_size(&self) -> Size<i32, Logical> {
|
||||
self.0.borrow().min_size
|
||||
}
|
||||
|
||||
fn max_size(&self) -> Size<i32, Logical> {
|
||||
self.0.borrow().max_size
|
||||
}
|
||||
|
||||
fn is_wl_surface(&self, _wl_surface: &WlSurface) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_preferred_scale_transform(&self, _scale: i32, _transform: Transform) {}
|
||||
|
||||
fn has_ssd(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn output_enter(&self, _output: &Output) {}
|
||||
|
||||
fn output_leave(&self, _output: &Output) {}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.0.borrow().pending_fullscreen
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user