mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
276 lines
8.9 KiB
Rust
276 lines
8.9 KiB
Rust
use gtk::glib;
|
|
use gtk::prelude::*;
|
|
use gtk::subclass::prelude::*;
|
|
use smithay::utils::Size;
|
|
|
|
use crate::cases::{Args, TestCase};
|
|
|
|
mod imp {
|
|
use std::cell::{Cell, OnceCell, RefCell};
|
|
use std::ptr::null;
|
|
use std::time::Duration;
|
|
|
|
use anyhow::{ensure, Context};
|
|
use gtk::gdk;
|
|
use gtk::prelude::*;
|
|
use niri::animation::Clock;
|
|
use niri::render_helpers::{resources, shaders};
|
|
use smithay::backend::egl::ffi::egl;
|
|
use smithay::backend::egl::EGLContext;
|
|
use smithay::backend::renderer::gles::GlesRenderer;
|
|
use smithay::backend::renderer::{Color32F, Frame, Renderer, Unbind};
|
|
use smithay::utils::{Physical, Rectangle, Scale, Transform};
|
|
|
|
use super::*;
|
|
|
|
type DynMakeTestCase = Box<dyn Fn(Args) -> 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>>>,
|
|
pub clock: RefCell<Clock>,
|
|
}
|
|
|
|
#[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();
|
|
|
|
let frame_clock = self.obj().frame_clock().unwrap();
|
|
let time = Duration::from_micros(frame_clock.frame_time() as u64);
|
|
self.clock.borrow_mut().set_unadjusted(time);
|
|
|
|
// 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();
|
|
let args = Args {
|
|
size: Size::from(size),
|
|
clock: self.clock.borrow().clone(),
|
|
};
|
|
make(args)
|
|
});
|
|
|
|
case.advance_animations(self.clock.borrow_mut().now());
|
|
|
|
let rect: Rectangle<i32, Physical> = Rectangle::from_size(Size::from(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(Color32F::from([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 mut renderer = GlesRenderer::new(egl_context).context("error creating GlesRenderer")?;
|
|
|
|
resources::init(&mut renderer);
|
|
shaders::init(&mut renderer);
|
|
|
|
Ok(renderer)
|
|
}
|
|
|
|
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(Args) -> T + 'static,
|
|
anim_adjustment: >k::Adjustment,
|
|
) -> Self {
|
|
let obj: Self = glib::Object::builder().build();
|
|
|
|
let make = move |args| Box::new(make_test_case(args)) as Box<dyn TestCase>;
|
|
let make_test_case = Box::new(make) as _;
|
|
let _ = obj.imp().make_test_case.set(make_test_case);
|
|
|
|
anim_adjustment.connect_value_changed({
|
|
let obj = obj.downgrade();
|
|
move |adj| {
|
|
if let Some(obj) = obj.upgrade() {
|
|
let mut clock = obj.imp().clock.borrow_mut();
|
|
let instantly = adj.value() == 0.0;
|
|
let rate = if instantly {
|
|
1.0
|
|
} else {
|
|
1.0 / adj.value().max(0.001)
|
|
};
|
|
clock.set_rate(rate);
|
|
clock.set_complete_instantly(instantly);
|
|
}
|
|
}
|
|
});
|
|
|
|
obj
|
|
}
|
|
}
|