mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-14 06:15:30 +00:00
LinuxKMS: Add support for synthetic display rotations (#4166)
This patch adds support for the `SLINT_KMS_ROTATION` environment variable, that instructs the Skia/FemtoVG renderers to rotate the scene before rendering.
This commit is contained in:
parent
b8ebc084b6
commit
3b51c8e30a
11 changed files with 217 additions and 53 deletions
|
@ -5,6 +5,10 @@ All notable changes to this project are documented in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
- LinuxKMS backend: Added support rendering output rotation via the `SLINT_KMS_ROTATION` environment variable.
|
||||||
|
|
||||||
### Slint Language
|
### Slint Language
|
||||||
|
|
||||||
- Fixed wrong text input in cupertino SpinBox
|
- Fixed wrong text input in cupertino SpinBox
|
||||||
|
|
|
@ -114,3 +114,21 @@ environment variables to configure support for different keyboards:
|
||||||
options section in
|
options section in
|
||||||
[xkeyboard-config(7)](https://manpages.debian.org/testing/xkb-data/xkeyboard-config.7.en.html) for a list of accepted option codes.
|
[xkeyboard-config(7)](https://manpages.debian.org/testing/xkb-data/xkeyboard-config.7.en.html) for a list of accepted option codes.
|
||||||
|
|
||||||
|
## Display Rotation
|
||||||
|
|
||||||
|
If your display's default orientation does not match the desired orientation of your user interface, then you can
|
||||||
|
set the `SLINT_KMS_ROTATION` environment variable to instruct Slint to rotate at rendering time. Supported values
|
||||||
|
are the rotation in degress: `0`, `90`, `180`, and `270`.
|
||||||
|
|
||||||
|
Note that this variable merely rotates the rendering output. If you're using a touch screen attached to the same
|
||||||
|
display, then you may need to configure it to also apply a rotation on the touch events generated. For configuring
|
||||||
|
libinput's `LIBINPUT_CALIBRATION_MATRIX` see the [libinput Documentation](https://wayland.freedesktop.org/libinput/doc/latest/device-configuration-via-udev.html#static-device-configuration-via-udev)
|
||||||
|
for a list of valid values. Values can typically be set by writing them into a rules file under `/etc/udev/rules.d`.
|
||||||
|
|
||||||
|
The following example configures libinput to apply a 90 degree clockwise rotation for any attached touch screen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'ENV{LIBINPUT_CALIBRATION_MATRIX}="0 -1 1 1 0 0"' > /etc/udev/rules.d/libinput.rules
|
||||||
|
udevadm control --reload-rules
|
||||||
|
udevadm trigger
|
||||||
|
```
|
|
@ -78,7 +78,7 @@ pub struct Backend {
|
||||||
&'a crate::DeviceOpener,
|
&'a crate::DeviceOpener,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>,
|
Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>,
|
||||||
i_slint_core::platform::PlatformError,
|
PlatformError,
|
||||||
>,
|
>,
|
||||||
sel_clipboard: RefCell<Option<String>>,
|
sel_clipboard: RefCell<Option<String>>,
|
||||||
clipboard: RefCell<Option<String>>,
|
clipboard: RefCell<Option<String>>,
|
||||||
|
@ -152,10 +152,7 @@ impl Backend {
|
||||||
impl i_slint_core::platform::Platform for Backend {
|
impl i_slint_core::platform::Platform for Backend {
|
||||||
fn create_window_adapter(
|
fn create_window_adapter(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<
|
) -> Result<std::rc::Rc<dyn i_slint_core::window::WindowAdapter>, PlatformError> {
|
||||||
std::rc::Rc<dyn i_slint_core::window::WindowAdapter>,
|
|
||||||
i_slint_core::platform::PlatformError,
|
|
||||||
> {
|
|
||||||
#[cfg(feature = "libseat")]
|
#[cfg(feature = "libseat")]
|
||||||
let device_accessor = |device: &std::path::Path| -> Result<Arc<dyn AsFd>, PlatformError> {
|
let device_accessor = |device: &std::path::Path| -> Result<Arc<dyn AsFd>, PlatformError> {
|
||||||
let device = self
|
let device = self
|
||||||
|
@ -189,8 +186,17 @@ impl i_slint_core::platform::Platform for Backend {
|
||||||
Ok(Arc::new(device))
|
Ok(Arc::new(device))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This could be per-screen, once we support multiple outputs
|
||||||
|
let rotation =
|
||||||
|
std::env::var("SLINT_KMS_ROTATION").map_or(Ok(Default::default()), |rot_str| {
|
||||||
|
rot_str
|
||||||
|
.as_str()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e| format!("Failed to parse SLINT_KMS_ROTATION: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
let renderer = (self.renderer_factory)(&device_accessor)?;
|
let renderer = (self.renderer_factory)(&device_accessor)?;
|
||||||
let adapter = FullscreenWindowAdapter::new(renderer)?;
|
let adapter = FullscreenWindowAdapter::new(renderer, rotation)?;
|
||||||
|
|
||||||
*self.window.borrow_mut() = Some(adapter.clone());
|
*self.window.borrow_mut() = Some(adapter.clone());
|
||||||
|
|
||||||
|
|
82
internal/backends/linuxkms/display.rs
Normal file
82
internal/backends/linuxkms/display.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
use i_slint_core::api::PhysicalSize;
|
||||||
|
|
||||||
|
pub trait Presenter {
|
||||||
|
// Present updated front-buffer to the screen
|
||||||
|
fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-femtovg"))]
|
||||||
|
pub mod egldisplay;
|
||||||
|
#[cfg(feature = "renderer-skia-vulkan")]
|
||||||
|
pub mod vulkandisplay;
|
||||||
|
|
||||||
|
/// This enum describes the way the output is supposed to be rotated to simulate
|
||||||
|
/// a screen rotation. This is implemented entirely inside the actual renderer.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub enum RenderingRotation {
|
||||||
|
/// No rotation
|
||||||
|
#[default]
|
||||||
|
NoRotation,
|
||||||
|
/// Rotate 90° to the left
|
||||||
|
Rotate90,
|
||||||
|
/// 180° rotation (upside-down)
|
||||||
|
Rotate180,
|
||||||
|
/// Rotate 90° to the right
|
||||||
|
Rotate270,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for RenderingRotation {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
let angle: usize = value.parse().map_err(|_| {
|
||||||
|
format!("Invalid value for rotation. Must be unsigned integral, found {value}")
|
||||||
|
})?;
|
||||||
|
Ok(match angle {
|
||||||
|
0 => Self::NoRotation,
|
||||||
|
90 => Self::Rotate90,
|
||||||
|
180 => Self::Rotate180,
|
||||||
|
270 => Self::Rotate270,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"Invalid value for rotation. Must be one of 0, 90, 180, or 270"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderingRotation {
|
||||||
|
pub fn screen_size_to_rotated_window_size(&self, screen_size: PhysicalSize) -> PhysicalSize {
|
||||||
|
match self {
|
||||||
|
RenderingRotation::NoRotation | RenderingRotation::Rotate180 => screen_size,
|
||||||
|
RenderingRotation::Rotate90 | RenderingRotation::Rotate270 => {
|
||||||
|
PhysicalSize::new(screen_size.height, screen_size.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn degrees(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
RenderingRotation::NoRotation => 0.,
|
||||||
|
RenderingRotation::Rotate90 => 90.,
|
||||||
|
RenderingRotation::Rotate180 => 180.,
|
||||||
|
RenderingRotation::Rotate270 => 270.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translation_after_rotation(&self, screen_size: PhysicalSize) -> (f32, f32) {
|
||||||
|
match self {
|
||||||
|
RenderingRotation::NoRotation => (0., 0.),
|
||||||
|
RenderingRotation::Rotate90 => (0., -(screen_size.width as f32)),
|
||||||
|
RenderingRotation::Rotate180 => {
|
||||||
|
(-(screen_size.width as f32), -(screen_size.height as f32))
|
||||||
|
}
|
||||||
|
RenderingRotation::Rotate270 => (-(screen_size.height as f32), 0.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,10 +15,13 @@ use i_slint_core::slice::Slice;
|
||||||
use i_slint_core::Property;
|
use i_slint_core::Property;
|
||||||
use i_slint_core::{platform::PlatformError, window::WindowAdapter};
|
use i_slint_core::{platform::PlatformError, window::WindowAdapter};
|
||||||
|
|
||||||
|
use crate::display::RenderingRotation;
|
||||||
|
|
||||||
pub trait FullscreenRenderer {
|
pub trait FullscreenRenderer {
|
||||||
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
|
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
|
||||||
fn render_and_present(
|
fn render_and_present(
|
||||||
&self,
|
&self,
|
||||||
|
rotation: RenderingRotation,
|
||||||
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
|
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
|
||||||
) -> Result<(), PlatformError>;
|
) -> Result<(), PlatformError>;
|
||||||
fn size(&self) -> PhysicalWindowSize;
|
fn size(&self) -> PhysicalWindowSize;
|
||||||
|
@ -28,6 +31,7 @@ pub struct FullscreenWindowAdapter {
|
||||||
window: i_slint_core::api::Window,
|
window: i_slint_core::api::Window,
|
||||||
renderer: Box<dyn FullscreenRenderer>,
|
renderer: Box<dyn FullscreenRenderer>,
|
||||||
needs_redraw: Cell<bool>,
|
needs_redraw: Cell<bool>,
|
||||||
|
rotation: RenderingRotation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowAdapter for FullscreenWindowAdapter {
|
impl WindowAdapter for FullscreenWindowAdapter {
|
||||||
|
@ -36,7 +40,7 @@ impl WindowAdapter for FullscreenWindowAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> i_slint_core::api::PhysicalSize {
|
fn size(&self) -> i_slint_core::api::PhysicalSize {
|
||||||
self.renderer.size()
|
self.rotation.screen_size_to_rotated_window_size(self.renderer.size())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
|
fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
|
||||||
|
@ -64,11 +68,15 @@ impl WindowAdapter for FullscreenWindowAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FullscreenWindowAdapter {
|
impl FullscreenWindowAdapter {
|
||||||
pub fn new(renderer: Box<dyn FullscreenRenderer>) -> Result<Rc<Self>, PlatformError> {
|
pub fn new(
|
||||||
|
renderer: Box<dyn FullscreenRenderer>,
|
||||||
|
rotation: RenderingRotation,
|
||||||
|
) -> Result<Rc<Self>, PlatformError> {
|
||||||
Ok(Rc::<FullscreenWindowAdapter>::new_cyclic(|self_weak| FullscreenWindowAdapter {
|
Ok(Rc::<FullscreenWindowAdapter>::new_cyclic(|self_weak| FullscreenWindowAdapter {
|
||||||
window: i_slint_core::api::Window::new(self_weak.clone()),
|
window: i_slint_core::api::Window::new(self_weak.clone()),
|
||||||
renderer,
|
renderer,
|
||||||
needs_redraw: Cell::new(true),
|
needs_redraw: Cell::new(true),
|
||||||
|
rotation,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +85,7 @@ impl FullscreenWindowAdapter {
|
||||||
mouse_position: Pin<&Property<Option<LogicalPosition>>>,
|
mouse_position: Pin<&Property<Option<LogicalPosition>>>,
|
||||||
) -> Result<(), PlatformError> {
|
) -> Result<(), PlatformError> {
|
||||||
if self.needs_redraw.replace(false) {
|
if self.needs_redraw.replace(false) {
|
||||||
self.renderer.render_and_present(&|item_renderer| {
|
self.renderer.render_and_present(self.rotation, &|item_renderer| {
|
||||||
if let Some(mouse_position) = mouse_position.get() {
|
if let Some(mouse_position) = mouse_position.get() {
|
||||||
item_renderer.save_state();
|
item_renderer.save_state();
|
||||||
item_renderer.translate(
|
item_renderer.translate(
|
||||||
|
|
|
@ -15,17 +15,7 @@ type DeviceOpener<'a> = dyn Fn(&std::path::Path) -> Result<std::sync::Arc<dyn As
|
||||||
+ 'a;
|
+ 'a;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod display {
|
mod display;
|
||||||
pub trait Presenter {
|
|
||||||
// Present updated front-buffer to the screen
|
|
||||||
fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-femtovg"))]
|
|
||||||
pub mod egldisplay;
|
|
||||||
#[cfg(feature = "renderer-skia-vulkan")]
|
|
||||||
pub mod vulkandisplay;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod renderer {
|
mod renderer {
|
||||||
|
|
|
@ -18,7 +18,7 @@ use glutin::{
|
||||||
surface::{SurfaceAttributesBuilder, WindowSurface},
|
surface::{SurfaceAttributesBuilder, WindowSurface},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::display::{egldisplay::EglDisplay, Presenter};
|
use crate::display::{egldisplay::EglDisplay, Presenter, RenderingRotation};
|
||||||
|
|
||||||
pub struct FemtoVGRendererAdapter {
|
pub struct FemtoVGRendererAdapter {
|
||||||
renderer: i_slint_renderer_femtovg::FemtoVGRenderer,
|
renderer: i_slint_renderer_femtovg::FemtoVGRenderer,
|
||||||
|
@ -143,16 +143,11 @@ unsafe impl i_slint_renderer_femtovg::OpenGLInterface for GlContextWrapper {
|
||||||
|
|
||||||
fn resize(
|
fn resize(
|
||||||
&self,
|
&self,
|
||||||
width: NonZeroU32,
|
_width: NonZeroU32,
|
||||||
height: NonZeroU32,
|
_height: NonZeroU32,
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
if self.egl_display.size.height != height.get()
|
// Ignore resize requests
|
||||||
|| self.egl_display.size.width != width.get()
|
Ok(())
|
||||||
{
|
|
||||||
Err("Resizing a fullscreen window is not supported".into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void {
|
fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||||
|
@ -187,11 +182,17 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for FemtoVGRendererAdapt
|
||||||
}
|
}
|
||||||
fn render_and_present(
|
fn render_and_present(
|
||||||
&self,
|
&self,
|
||||||
|
rotation: RenderingRotation,
|
||||||
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
|
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
|
||||||
) -> Result<(), PlatformError> {
|
) -> Result<(), PlatformError> {
|
||||||
self.renderer.render_with_post_callback(Some(&|item_renderer| {
|
self.renderer.render_transformed_with_post_callback(
|
||||||
draw_mouse_cursor_callback(item_renderer);
|
rotation.degrees(),
|
||||||
}))
|
rotation.translation_after_rotation(self.size),
|
||||||
|
self.size,
|
||||||
|
Some(&|item_renderer| {
|
||||||
|
draw_mouse_cursor_callback(item_renderer);
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
fn size(&self) -> i_slint_core::api::PhysicalSize {
|
fn size(&self) -> i_slint_core::api::PhysicalSize {
|
||||||
self.size
|
self.size
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
use crate::display::RenderingRotation;
|
||||||
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
|
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
|
||||||
use i_slint_core::item_rendering::ItemRenderer;
|
use i_slint_core::item_rendering::ItemRenderer;
|
||||||
use i_slint_core::platform::PlatformError;
|
use i_slint_core::platform::PlatformError;
|
||||||
|
@ -95,11 +96,17 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter
|
||||||
}
|
}
|
||||||
fn render_and_present(
|
fn render_and_present(
|
||||||
&self,
|
&self,
|
||||||
|
rotation: RenderingRotation,
|
||||||
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
|
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
|
||||||
) -> Result<(), PlatformError> {
|
) -> Result<(), PlatformError> {
|
||||||
self.renderer.render_with_post_callback(Some(&|item_renderer| {
|
self.renderer.render_transformed_with_post_callback(
|
||||||
draw_mouse_cursor_callback(item_renderer);
|
rotation.degrees(),
|
||||||
}))?;
|
rotation.translation_after_rotation(self.size),
|
||||||
|
self.size,
|
||||||
|
Some(&|item_renderer| {
|
||||||
|
draw_mouse_cursor_callback(item_renderer);
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
if let Some(presenter) = self.presenter.as_ref() {
|
if let Some(presenter) = self.presenter.as_ref() {
|
||||||
presenter.present()?;
|
presenter.present()?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,11 +171,19 @@ impl FemtoVGRenderer {
|
||||||
|
|
||||||
/// Render the scene using OpenGL.
|
/// Render the scene using OpenGL.
|
||||||
pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||||
self.internal_render_with_post_callback(None)
|
self.internal_render_with_post_callback(
|
||||||
|
0.,
|
||||||
|
(0., 0.),
|
||||||
|
self.window_adapter()?.window().size(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_render_with_post_callback(
|
fn internal_render_with_post_callback(
|
||||||
&self,
|
&self,
|
||||||
|
rotation_angle_degrees: f32,
|
||||||
|
translation: (f32, f32),
|
||||||
|
surface_size: i_slint_core::api::PhysicalSize,
|
||||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||||
self.opengl_context.ensure_current()?;
|
self.opengl_context.ensure_current()?;
|
||||||
|
@ -193,10 +201,10 @@ impl FemtoVGRenderer {
|
||||||
|
|
||||||
let window_adapter = self.window_adapter()?;
|
let window_adapter = self.window_adapter()?;
|
||||||
let window = window_adapter.window();
|
let window = window_adapter.window();
|
||||||
let size = window.size();
|
let window_size = window.size();
|
||||||
|
|
||||||
let Some((width, height)): Option<(NonZeroU32, NonZeroU32)> =
|
let Some((width, height)): Option<(NonZeroU32, NonZeroU32)> =
|
||||||
size.width.try_into().ok().zip(size.height.try_into().ok())
|
window_size.width.try_into().ok().zip(window_size.height.try_into().ok())
|
||||||
else {
|
else {
|
||||||
// Nothing to render
|
// Nothing to render
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -215,20 +223,27 @@ impl FemtoVGRenderer {
|
||||||
// We pass an integer that is greater than or equal to the scale factor as
|
// We pass an integer that is greater than or equal to the scale factor as
|
||||||
// dpi / device pixel ratio as the anti-alias of femtovg needs that to draw text clearly.
|
// dpi / device pixel ratio as the anti-alias of femtovg needs that to draw text clearly.
|
||||||
// We need to care about that `ceil()` when calculating metrics.
|
// We need to care about that `ceil()` when calculating metrics.
|
||||||
femtovg_canvas.set_size(width.get(), height.get(), scale);
|
femtovg_canvas.set_size(surface_size.width, surface_size.height, scale);
|
||||||
|
|
||||||
// Clear with window background if it is a solid color otherwise it will drawn as gradient
|
// Clear with window background if it is a solid color otherwise it will drawn as gradient
|
||||||
if let Some(Brush::SolidColor(clear_color)) = window_background_brush {
|
if let Some(Brush::SolidColor(clear_color)) = window_background_brush {
|
||||||
femtovg_canvas.clear_rect(
|
femtovg_canvas.clear_rect(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
width.get(),
|
surface_size.width,
|
||||||
height.get(),
|
surface_size.height,
|
||||||
self::itemrenderer::to_femtovg_color(&clear_color),
|
self::itemrenderer::to_femtovg_color(&clear_color),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut femtovg_canvas = self.canvas.borrow_mut();
|
||||||
|
femtovg_canvas.reset();
|
||||||
|
femtovg_canvas.rotate(rotation_angle_degrees.to_radians());
|
||||||
|
femtovg_canvas.translate(translation.0, translation.1);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(notifier_fn) = self.rendering_notifier.borrow_mut().as_mut() {
|
if let Some(notifier_fn) = self.rendering_notifier.borrow_mut().as_mut() {
|
||||||
let mut femtovg_canvas = self.canvas.borrow_mut();
|
let mut femtovg_canvas = self.canvas.borrow_mut();
|
||||||
// For the BeforeRendering rendering notifier callback it's important that this happens *after* clearing
|
// For the BeforeRendering rendering notifier callback it's important that this happens *after* clearing
|
||||||
|
@ -262,7 +277,7 @@ impl FemtoVGRenderer {
|
||||||
Some(brush) => {
|
Some(brush) => {
|
||||||
item_renderer.draw_rect(
|
item_renderer.draw_rect(
|
||||||
i_slint_core::lengths::logical_size_from_api(
|
i_slint_core::lengths::logical_size_from_api(
|
||||||
size.to_logical(window_inner.scale_factor()),
|
window.size().to_logical(window_inner.scale_factor()),
|
||||||
),
|
),
|
||||||
brush,
|
brush,
|
||||||
);
|
);
|
||||||
|
@ -549,17 +564,28 @@ impl Drop for FemtoVGRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FemtoVGRendererExt {
|
pub trait FemtoVGRendererExt {
|
||||||
fn render_with_post_callback(
|
fn render_transformed_with_post_callback(
|
||||||
&self,
|
&self,
|
||||||
|
rotation_angle_degrees: f32,
|
||||||
|
translation: (f32, f32),
|
||||||
|
surface_size: i_slint_core::api::PhysicalSize,
|
||||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FemtoVGRendererExt for FemtoVGRenderer {
|
impl FemtoVGRendererExt for FemtoVGRenderer {
|
||||||
fn render_with_post_callback(
|
fn render_transformed_with_post_callback(
|
||||||
&self,
|
&self,
|
||||||
|
rotation_angle_degrees: f32,
|
||||||
|
translation: (f32, f32),
|
||||||
|
surface_size: i_slint_core::api::PhysicalSize,
|
||||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||||
self.internal_render_with_post_callback(post_render_cb)
|
self.internal_render_with_post_callback(
|
||||||
|
rotation_angle_degrees,
|
||||||
|
translation,
|
||||||
|
surface_size,
|
||||||
|
post_render_cb,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,11 +176,16 @@ impl SkiaRenderer {
|
||||||
|
|
||||||
/// Render the scene in the previously associated window.
|
/// Render the scene in the previously associated window.
|
||||||
pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||||
self.internal_render_with_post_callback(None)
|
let window_adapter = self.window_adapter()?;
|
||||||
|
let size = window_adapter.window().size();
|
||||||
|
self.internal_render_with_post_callback(0., (0., 0.), size, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_render_with_post_callback(
|
fn internal_render_with_post_callback(
|
||||||
&self,
|
&self,
|
||||||
|
rotation_angle_degrees: f32,
|
||||||
|
translation: (f32, f32),
|
||||||
|
surace_size: PhysicalWindowSize,
|
||||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||||
let surface = self.surface.borrow();
|
let surface = self.surface.borrow();
|
||||||
|
@ -202,10 +207,12 @@ impl SkiaRenderer {
|
||||||
|
|
||||||
let window_adapter = self.window_adapter()?;
|
let window_adapter = self.window_adapter()?;
|
||||||
let window = window_adapter.window();
|
let window = window_adapter.window();
|
||||||
let size = window.size();
|
|
||||||
let window_inner = WindowInner::from_pub(window);
|
let window_inner = WindowInner::from_pub(window);
|
||||||
|
|
||||||
surface.render(size, &|skia_canvas, mut gr_context| {
|
surface.render(surace_size, &|skia_canvas, mut gr_context| {
|
||||||
|
skia_canvas.rotate(rotation_angle_degrees, None);
|
||||||
|
skia_canvas.translate(translation);
|
||||||
|
|
||||||
window_inner.draw_contents(|components| {
|
window_inner.draw_contents(|components| {
|
||||||
let window_background_brush =
|
let window_background_brush =
|
||||||
window_inner.window_item().map(|w| w.as_pin_ref().background());
|
window_inner.window_item().map(|w| w.as_pin_ref().background());
|
||||||
|
@ -247,7 +254,7 @@ impl SkiaRenderer {
|
||||||
Some(brush @ _) => {
|
Some(brush @ _) => {
|
||||||
item_renderer.draw_rect(
|
item_renderer.draw_rect(
|
||||||
i_slint_core::lengths::logical_size_from_api(
|
i_slint_core::lengths::logical_size_from_api(
|
||||||
size.to_logical(window_inner.scale_factor()),
|
window.size().to_logical(window_inner.scale_factor()),
|
||||||
),
|
),
|
||||||
brush,
|
brush,
|
||||||
);
|
);
|
||||||
|
@ -544,17 +551,28 @@ pub trait Surface {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SkiaRendererExt {
|
pub trait SkiaRendererExt {
|
||||||
fn render_with_post_callback(
|
fn render_transformed_with_post_callback(
|
||||||
&self,
|
&self,
|
||||||
|
rotation_angle_degrees: f32,
|
||||||
|
translation: (f32, f32),
|
||||||
|
surface_size: PhysicalWindowSize,
|
||||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SkiaRendererExt for SkiaRenderer {
|
impl SkiaRendererExt for SkiaRenderer {
|
||||||
fn render_with_post_callback(
|
fn render_transformed_with_post_callback(
|
||||||
&self,
|
&self,
|
||||||
|
rotation_angle_degrees: f32,
|
||||||
|
translation: (f32, f32),
|
||||||
|
surface_size: PhysicalWindowSize,
|
||||||
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
|
||||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||||
self.internal_render_with_post_callback(post_render_cb)
|
self.internal_render_with_post_callback(
|
||||||
|
rotation_angle_degrees,
|
||||||
|
translation,
|
||||||
|
surface_size,
|
||||||
|
post_render_cb,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ impl super::Surface for OpenGLSurface {
|
||||||
let (current_glutin_context, glutin_surface) =
|
let (current_glutin_context, glutin_surface) =
|
||||||
Self::init_glutin(window_handle, display_handle, width, height)?;
|
Self::init_glutin(window_handle, display_handle, width, height)?;
|
||||||
|
|
||||||
|
glutin_surface.resize(¤t_glutin_context, width, height);
|
||||||
|
|
||||||
let fb_info = {
|
let fb_info = {
|
||||||
use glow::HasContext;
|
use glow::HasContext;
|
||||||
|
|
||||||
|
@ -151,7 +153,9 @@ impl super::Surface for OpenGLSurface {
|
||||||
|
|
||||||
let skia_canvas = surface.canvas();
|
let skia_canvas = surface.canvas();
|
||||||
|
|
||||||
|
skia_canvas.save();
|
||||||
callback(skia_canvas, Some(gr_context));
|
callback(skia_canvas, Some(gr_context));
|
||||||
|
skia_canvas.restore();
|
||||||
|
|
||||||
self.glutin_surface.swap_buffers(¤t_context).map_err(|glutin_error| {
|
self.glutin_surface.swap_buffers(¤t_context).map_err(|glutin_error| {
|
||||||
format!("Skia OpenGL Renderer: Error swapping buffers: {glutin_error}").into()
|
format!("Skia OpenGL Renderer: Error swapping buffers: {glutin_error}").into()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue