mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-02 09:52:38 +00:00
Introduce error handling in the FemtoVG and Skia renderers (#2402)
Avoid unwrap() and expect() and instead propagate errors all the way down to run_event_loop(), show(), and hide() in the Slint AIP.
This commit is contained in:
parent
7f95614a98
commit
8ffb5131c7
28 changed files with 627 additions and 357 deletions
|
@ -53,8 +53,9 @@ impl WindowAdapterSealed for CppWindowAdapter {
|
|||
unsafe { (self.show)(self.user_data) };
|
||||
Ok(())
|
||||
}
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), PlatformError> {
|
||||
unsafe { (self.hide)(self.user_data) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
|
@ -230,7 +231,7 @@ pub unsafe extern "C" fn slint_skia_renderer_show_win32(
|
|||
RawWindowHandle::Win32(init_raw!(raw_window_handle::Win32WindowHandle { hwnd, hinstance })),
|
||||
RawDisplayHandle::Windows(raw_window_handle::WindowsDisplayHandle::empty()),
|
||||
);
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height })
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -248,7 +249,7 @@ pub unsafe extern "C" fn slint_skia_renderer_show_x11(
|
|||
RawWindowHandle::Xcb(init_raw!(XcbWindowHandle { window, visual_id })),
|
||||
RawDisplayHandle::Xcb(init_raw!(XcbDisplayHandle { connection, screen })),
|
||||
);
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height })
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -265,7 +266,7 @@ pub unsafe extern "C" fn slint_skia_renderer_show_wayland(
|
|||
RawWindowHandle::Wayland(init_raw!(WaylandWindowHandle { surface })),
|
||||
RawDisplayHandle::Wayland(init_raw!(WaylandDisplayHandle { display })),
|
||||
);
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height })
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -282,25 +283,25 @@ pub unsafe extern "C" fn slint_skia_renderer_show_appkit(
|
|||
RawWindowHandle::AppKit(init_raw!(AppKitWindowHandle { ns_view, ns_window })),
|
||||
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()),
|
||||
);
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height })
|
||||
r.show(handle, PhysicalSize { width: size.width, height: size.height }).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_skia_renderer_hide(r: SkiaRendererOpaque) {
|
||||
let r = &*(r as *const SkiaRenderer);
|
||||
r.hide()
|
||||
r.hide().unwrap()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_skia_renderer_resize(r: SkiaRendererOpaque, size: IntSize) {
|
||||
let r = &*(r as *const SkiaRenderer);
|
||||
r.resize_event(PhysicalSize { width: size.width, height: size.height });
|
||||
r.resize_event(PhysicalSize { width: size.width, height: size.height }).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_skia_renderer_render(r: SkiaRendererOpaque, size: IntSize) {
|
||||
let r = &*(r as *const SkiaRenderer);
|
||||
r.render(PhysicalSize { width: size.width, height: size.height });
|
||||
r.render(PhysicalSize { width: size.width, height: size.height }).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
|
@ -498,7 +498,7 @@ declare_types! {
|
|||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
window_adapter.hide();
|
||||
window_adapter.hide().unwrap();
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ pub mod generated_code {
|
|||
|
||||
/// Marks the window of this component to be hidden on the screen. This de-registers
|
||||
/// the window from the windowing system and it will not receive any further events.
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), crate::PlatformError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
|
|
|
@ -191,8 +191,8 @@ impl WrappedInstance {
|
|||
}
|
||||
/// Hides this instance and prevents further updates of the canvas element.
|
||||
#[wasm_bindgen]
|
||||
pub fn hide(&self) {
|
||||
self.0.hide();
|
||||
pub fn hide(&self) -> Result<(), JsValue> {
|
||||
self.0.hide().map_err(|e| -> JsValue { format!("{e}").into() })
|
||||
}
|
||||
|
||||
/// THIS FUNCTION IS NOT PART THE PUBLIC API!
|
||||
|
|
|
@ -48,7 +48,7 @@ pub fn main() {
|
|||
let weak_window = main_window.as_weak();
|
||||
main_window.on_popup_confirmed(move || {
|
||||
let window = weak_window.unwrap();
|
||||
window.hide();
|
||||
window.hide().unwrap();
|
||||
});
|
||||
|
||||
{
|
||||
|
|
|
@ -1498,7 +1498,7 @@ impl WindowAdapterSealed for QtWindow {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.rendering_metrics_collector.take();
|
||||
let widget_ptr = self.widget_ptr();
|
||||
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
||||
|
@ -1507,6 +1507,7 @@ impl WindowAdapterSealed for QtWindow {
|
|||
// visible windows, and ends the application if needed
|
||||
QEventLoopLocker();
|
||||
}};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
|
@ -1930,9 +1931,10 @@ impl Renderer for QtWindow {
|
|||
&self,
|
||||
component: ComponentRef,
|
||||
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
|
||||
) {
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
// Invalidate caches:
|
||||
self.cache.component_destroyed(component);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,8 +60,9 @@ impl WindowAdapterSealed for TestingWindow {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.shown.set(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn renderer(&self) -> &dyn Renderer {
|
||||
|
|
|
@ -31,7 +31,7 @@ pub(crate) static QUIT_ON_LAST_WINDOW_CLOSED: std::sync::atomic::AtomicBool =
|
|||
pub trait WinitWindow: WindowAdapter {
|
||||
fn currently_pressed_key_code(&self) -> &Cell<Option<winit::event::VirtualKeyCode>>;
|
||||
/// Returns true if during the drawing request_redraw() was called.
|
||||
fn draw(&self) -> bool;
|
||||
fn draw(&self) -> Result<bool, i_slint_core::platform::PlatformError>;
|
||||
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window));
|
||||
fn constraints(&self) -> (corelib::layout::LayoutInfo, corelib::layout::LayoutInfo);
|
||||
fn set_constraints(
|
||||
|
@ -40,7 +40,12 @@ pub trait WinitWindow: WindowAdapter {
|
|||
);
|
||||
|
||||
/// Called by the event loop when a WindowEvent::Resized is received.
|
||||
fn resize_event(&self, _size: winit::dpi::PhysicalSize<u32>) {}
|
||||
fn resize_event(
|
||||
&self,
|
||||
_size: winit::dpi::PhysicalSize<u32>,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return true if the proxy element used for input method has the focus
|
||||
fn input_method_focused(&self) -> bool {
|
||||
|
@ -267,15 +272,15 @@ fn process_window_event(
|
|||
event: WindowEvent,
|
||||
cursor_pos: &mut LogicalPoint,
|
||||
pressed: &mut bool,
|
||||
) {
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let runtime_window = WindowInner::from_pub(window.window());
|
||||
match event {
|
||||
WindowEvent::Resized(size) => {
|
||||
window.resize_event(size);
|
||||
window.resize_event(size)?;
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
if runtime_window.request_close() {
|
||||
window.hide();
|
||||
window.hide()?;
|
||||
}
|
||||
}
|
||||
WindowEvent::ReceivedCharacter(ch) => {
|
||||
|
@ -293,7 +298,7 @@ fn process_window_event(
|
|||
{
|
||||
ch
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
ch
|
||||
|
@ -437,7 +442,7 @@ fn process_window_event(
|
|||
runtime_window.set_window_item_geometry(LogicalSize::new(size.width, size.height));
|
||||
runtime_window.set_scale_factor(scale_factor as f32);
|
||||
// Resize the underlying graphics surface
|
||||
window.resize_event(*new_inner_size);
|
||||
window.resize_event(*new_inner_size)?;
|
||||
}
|
||||
}
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
|
@ -445,6 +450,7 @@ fn process_window_event(
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the event loop and renders the items in the provided `component` in its
|
||||
|
@ -493,110 +499,142 @@ pub fn run() -> Result<(), corelib::platform::PlatformError> {
|
|||
let mut cursor_pos = LogicalPoint::default();
|
||||
let mut pressed = false;
|
||||
|
||||
let mut run_fn = move |event: Event<CustomEvent>, control_flow: &mut ControlFlow| match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
if let Some(window) = window_by_id(window_id) {
|
||||
process_window_event(window, event, &mut cursor_pos, &mut pressed);
|
||||
};
|
||||
}
|
||||
let outer_event_loop_error = Rc::new(RefCell::new(None));
|
||||
let inner_event_loop_error = outer_event_loop_error.clone();
|
||||
|
||||
Event::RedrawRequested(id) => {
|
||||
if let Some(window) = window_by_id(id) {
|
||||
if let Ok(pos) = windows_with_pending_redraw_requests.binary_search(&id) {
|
||||
windows_with_pending_redraw_requests.remove(pos);
|
||||
}
|
||||
let redraw_requested_during_draw = window.draw();
|
||||
if redraw_requested_during_draw {
|
||||
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
|
||||
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
|
||||
// soon as possible.
|
||||
*control_flow = ControlFlow::Poll;
|
||||
}
|
||||
let mut run_fn = move |event: Event<CustomEvent>, control_flow: &mut ControlFlow| {
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
if let Some(window) = window_by_id(window_id) {
|
||||
*inner_event_loop_error.borrow_mut() =
|
||||
process_window_event(window, event, &mut cursor_pos, &mut pressed).err();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Event::UserEvent(CustomEvent::UpdateWindowProperties(window_id)) => {
|
||||
if let Err(insert_pos) = windows_with_pending_property_updates.binary_search(&window_id)
|
||||
{
|
||||
windows_with_pending_property_updates.insert(insert_pos, window_id);
|
||||
}
|
||||
}
|
||||
Event::UserEvent(CustomEvent::WindowHidden) => {
|
||||
if QUIT_ON_LAST_WINDOW_CLOSED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let window_count = ALL_WINDOWS.with(|windows| windows.borrow().len());
|
||||
if window_count == 0 {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::UserEvent(CustomEvent::Exit) => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
|
||||
Event::UserEvent(CustomEvent::UserEvent(user)) => {
|
||||
user();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Event::UserEvent(CustomEvent::WakeEventLoopWorkaround) => {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
}
|
||||
|
||||
Event::NewEvents(_) => {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
windows_with_pending_redraw_requests.clear();
|
||||
ALL_WINDOWS.with(|windows| {
|
||||
for (window_id, window_weak) in windows.borrow().iter() {
|
||||
if window_weak.upgrade().map_or(false, |window| window.take_pending_redraw()) {
|
||||
if let Err(insert_pos) =
|
||||
windows_with_pending_redraw_requests.binary_search(window_id)
|
||||
{
|
||||
windows_with_pending_redraw_requests.insert(insert_pos, *window_id);
|
||||
Event::RedrawRequested(id) => {
|
||||
if let Some(window) = window_by_id(id) {
|
||||
if let Ok(pos) = windows_with_pending_redraw_requests.binary_search(&id) {
|
||||
windows_with_pending_redraw_requests.remove(pos);
|
||||
}
|
||||
match window.draw() {
|
||||
Ok(redraw_requested_during_draw) => {
|
||||
if redraw_requested_during_draw {
|
||||
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
|
||||
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
|
||||
// soon as possible.
|
||||
*control_flow = ControlFlow::Poll;
|
||||
}
|
||||
}
|
||||
Err(rendering_error) => {
|
||||
*inner_event_loop_error.borrow_mut() = Some(rendering_error)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Event::UserEvent(CustomEvent::UpdateWindowProperties(window_id)) => {
|
||||
if let Err(insert_pos) =
|
||||
windows_with_pending_property_updates.binary_search(&window_id)
|
||||
{
|
||||
windows_with_pending_property_updates.insert(insert_pos, window_id);
|
||||
}
|
||||
}
|
||||
Event::UserEvent(CustomEvent::WindowHidden) => {
|
||||
if QUIT_ON_LAST_WINDOW_CLOSED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let window_count = ALL_WINDOWS.with(|windows| windows.borrow().len());
|
||||
if window_count == 0 {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
corelib::platform::update_timers_and_animations();
|
||||
}
|
||||
|
||||
Event::MainEventsCleared => {
|
||||
for window in windows_with_pending_property_updates.drain(..).filter_map(window_by_id) {
|
||||
WindowInner::from_pub(window.window()).update_window_properties();
|
||||
}
|
||||
}
|
||||
|
||||
Event::RedrawEventsCleared => {
|
||||
if *control_flow != ControlFlow::Exit
|
||||
&& ALL_WINDOWS.with(|windows| {
|
||||
windows.borrow().iter().any(|(_, w)| {
|
||||
w.upgrade().map_or(false, |w| w.window().has_active_animations())
|
||||
})
|
||||
})
|
||||
{
|
||||
Event::UserEvent(CustomEvent::Exit) => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
|
||||
Event::UserEvent(CustomEvent::UserEvent(user)) => {
|
||||
user();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Event::UserEvent(CustomEvent::WakeEventLoopWorkaround) => {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
}
|
||||
|
||||
for window in windows_with_pending_redraw_requests.drain(..).filter_map(window_by_id) {
|
||||
let redraw_requested_during_draw = window.draw();
|
||||
if redraw_requested_during_draw {
|
||||
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
|
||||
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
|
||||
// soon as possible.
|
||||
Event::NewEvents(_) => {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
windows_with_pending_redraw_requests.clear();
|
||||
ALL_WINDOWS.with(|windows| {
|
||||
for (window_id, window_weak) in windows.borrow().iter() {
|
||||
if window_weak
|
||||
.upgrade()
|
||||
.map_or(false, |window| window.take_pending_redraw())
|
||||
{
|
||||
if let Err(insert_pos) =
|
||||
windows_with_pending_redraw_requests.binary_search(window_id)
|
||||
{
|
||||
windows_with_pending_redraw_requests.insert(insert_pos, *window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
corelib::platform::update_timers_and_animations();
|
||||
}
|
||||
|
||||
Event::MainEventsCleared => {
|
||||
for window in
|
||||
windows_with_pending_property_updates.drain(..).filter_map(window_by_id)
|
||||
{
|
||||
WindowInner::from_pub(window.window()).update_window_properties();
|
||||
}
|
||||
}
|
||||
|
||||
Event::RedrawEventsCleared => {
|
||||
if *control_flow != ControlFlow::Exit
|
||||
&& ALL_WINDOWS.with(|windows| {
|
||||
windows.borrow().iter().any(|(_, w)| {
|
||||
w.upgrade().map_or(false, |w| w.window().has_active_animations())
|
||||
})
|
||||
})
|
||||
{
|
||||
*control_flow = ControlFlow::Poll;
|
||||
}
|
||||
}
|
||||
|
||||
if *control_flow == ControlFlow::Wait {
|
||||
if let Some(next_timer) = corelib::platform::duration_until_next_timer_update() {
|
||||
*control_flow = ControlFlow::WaitUntil(instant::Instant::now() + next_timer);
|
||||
for window in
|
||||
windows_with_pending_redraw_requests.drain(..).filter_map(window_by_id)
|
||||
{
|
||||
match window.draw() {
|
||||
Ok(redraw_requested_during_draw) => {
|
||||
if redraw_requested_during_draw {
|
||||
// If during rendering a new redraw_request() was issued (for example in a rendering notifier callback), then
|
||||
// pretend that an animation is running, so that we return Poll from the event loop to ensure a repaint as
|
||||
// soon as possible.
|
||||
*control_flow = ControlFlow::Poll;
|
||||
}
|
||||
}
|
||||
Err(rendering_error) => {
|
||||
*inner_event_loop_error.borrow_mut() = Some(rendering_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *control_flow == ControlFlow::Wait {
|
||||
if let Some(next_timer) = corelib::platform::duration_until_next_timer_update()
|
||||
{
|
||||
*control_flow =
|
||||
ControlFlow::WaitUntil(instant::Instant::now() + next_timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if inner_event_loop_error.borrow().is_some() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -620,6 +658,10 @@ pub fn run() -> Result<(), corelib::platform::PlatformError> {
|
|||
// Winit does not support creating multiple instances of the event loop.
|
||||
let nre = NotRunningEventLoop { clipboard, instance: winit_loop, event_loop_proxy };
|
||||
MAYBE_LOOP_INSTANCE.with(|loop_instance| *loop_instance.borrow_mut() = Some(nre));
|
||||
|
||||
if let Some(error) = outer_event_loop_error.borrow_mut().take() {
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -145,37 +145,43 @@ impl<Renderer: WinitCompatibleRenderer + 'static> GLWindow<Renderer> {
|
|||
}
|
||||
}
|
||||
|
||||
fn unmap(&self) {
|
||||
fn unmap(&self) -> Result<(), PlatformError> {
|
||||
let old_mapped = match self.map_state.replace(GraphicsWindowBackendState::Unmapped {
|
||||
requested_position: None,
|
||||
requested_size: None,
|
||||
}) {
|
||||
GraphicsWindowBackendState::Unmapped { .. } => return,
|
||||
GraphicsWindowBackendState::Unmapped { .. } => return Ok(()),
|
||||
GraphicsWindowBackendState::Mapped(old_mapped) => old_mapped,
|
||||
};
|
||||
|
||||
crate::event_loop::unregister_window(old_mapped.winit_window.id());
|
||||
|
||||
self.renderer.hide();
|
||||
self.renderer.hide()
|
||||
}
|
||||
|
||||
fn call_with_event_loop(&self, callback: fn(&Self)) {
|
||||
fn call_with_event_loop(
|
||||
&self,
|
||||
callback: fn(&Self) -> Result<(), PlatformError>,
|
||||
) -> Result<(), PlatformError> {
|
||||
// With wasm, winit's `run()` consumes the event loop and access to it from outside the event handler yields
|
||||
// loop and thus ends up trying to create a new event loop instance, which panics in winit. Instead, forward
|
||||
// the call to be invoked from within the event loop
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
corelib::api::invoke_from_event_loop({
|
||||
return corelib::api::invoke_from_event_loop({
|
||||
let self_weak = send_wrapper::SendWrapper::new(self.self_weak.clone());
|
||||
|
||||
move || {
|
||||
if let Some(this) = self_weak.take().upgrade() {
|
||||
callback(&this)
|
||||
// Can't propagate the returned error because we're in an async callback, so throw.
|
||||
callback(&this).unwrap()
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
.map_err(|_| {
|
||||
format!("internal error in winit backend: invoke_from_event_loop failed").into()
|
||||
});
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
callback(self)
|
||||
return callback(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,17 +195,17 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
|
|||
}
|
||||
|
||||
/// Draw the items of the specified `component` in the given window.
|
||||
fn draw(&self) -> bool {
|
||||
fn draw(&self) -> Result<bool, PlatformError> {
|
||||
let window = match self.borrow_mapped_window() {
|
||||
Some(window) => window,
|
||||
None => return false, // caller bug, doesn't make sense to call draw() when not mapped
|
||||
None => return Ok(false), // caller bug, doesn't make sense to call draw() when not mapped
|
||||
};
|
||||
|
||||
self.pending_redraw.set(false);
|
||||
|
||||
self.renderer.render(physical_size_to_slint(&window.winit_window.inner_size()));
|
||||
self.renderer.render(physical_size_to_slint(&window.winit_window.inner_size()))?;
|
||||
|
||||
self.pending_redraw.get()
|
||||
Ok(self.pending_redraw.get())
|
||||
}
|
||||
|
||||
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window)) {
|
||||
|
@ -231,7 +237,7 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
|
|||
}
|
||||
}
|
||||
|
||||
fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) {
|
||||
fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> {
|
||||
// slint::Window::set_size will call set_size() on this type, which would call
|
||||
// set_inner_size on the winit Window. On Windows that triggers an new resize event
|
||||
// in the next event loop iteration for mysterious reasons, with slightly different sizes.
|
||||
|
@ -248,7 +254,9 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WinitWindow for GLWindow<Rende
|
|||
if size.width > 0 && size.height > 0 {
|
||||
let physical_size = physical_size_to_slint(&size);
|
||||
self.window.set_size(physical_size);
|
||||
self.renderer.resize_event(physical_size);
|
||||
self.renderer.resize_event(physical_size)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,8 +290,10 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
});
|
||||
});
|
||||
Ok(()) // Doesn't matter if the eventloop is already closed, nothing to update then.
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn apply_window_properties(&self, window_item: Pin<&i_slint_core::items::WindowItem>) {
|
||||
|
@ -402,7 +412,7 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
GraphicsWindowBackendState::Unmapped { requested_position, requested_size } => {
|
||||
(requested_position.clone(), requested_size.clone())
|
||||
}
|
||||
GraphicsWindowBackendState::Mapped(_) => return,
|
||||
GraphicsWindowBackendState::Mapped(_) => return Ok(()),
|
||||
};
|
||||
|
||||
let mut window_builder = winit::window::WindowBuilder::new();
|
||||
|
@ -460,21 +470,34 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let html_canvas = {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let canvas = web_sys::window()
|
||||
.unwrap()
|
||||
web_sys::window()
|
||||
.ok_or_else(|| "winit backend: Could not retrieve DOM window".to_string())?
|
||||
.document()
|
||||
.unwrap()
|
||||
.ok_or_else(|| "winit backend: Could not retrieve DOM document".to_string())?
|
||||
.get_element_by_id(&self_.canvas_id)
|
||||
.unwrap()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"winit backend: Could not retrieve existing HTML Canvas element '{}'",
|
||||
self_.canvas_id
|
||||
)
|
||||
})?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.unwrap();
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"winit backend: Specified DOM element '{}' is not a HTML Canvas",
|
||||
self_.canvas_id
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let existing_canvas_size = winit::dpi::LogicalSize::new(
|
||||
canvas.client_width() as f32,
|
||||
canvas.client_height() as f32,
|
||||
html_canvas.client_width() as f32,
|
||||
html_canvas.client_height() as f32,
|
||||
);
|
||||
|
||||
// Try to maintain the existing size of the canvas element. A window created with winit
|
||||
|
@ -533,26 +556,15 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let window_builder = {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let canvas = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id(&self_.canvas_id)
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.unwrap();
|
||||
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
window_builder.with_canvas(Some(canvas.clone()))
|
||||
window_builder.with_canvas(Some(html_canvas.clone()))
|
||||
};
|
||||
|
||||
let winit_window = self_.renderer.show(
|
||||
window_builder,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
&self_.canvas_id,
|
||||
);
|
||||
)?;
|
||||
|
||||
WindowInner::from_pub(&self_.window).set_scale_factor(
|
||||
scale_factor_override.unwrap_or_else(|| winit_window.scale_factor()) as _,
|
||||
|
@ -570,13 +582,13 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
}));
|
||||
|
||||
crate::event_loop::register_window(id, self_.self_weak.upgrade().unwrap());
|
||||
});
|
||||
Ok(())
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), PlatformError> {
|
||||
self.call_with_event_loop(|self_| {
|
||||
self_.unmap();
|
||||
self_.unmap()?;
|
||||
|
||||
/* FIXME:
|
||||
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
|
||||
|
@ -587,8 +599,9 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
.event_loop_proxy()
|
||||
.send_event(crate::event_loop::CustomEvent::WindowHidden)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
.ok(); // It's okay to call hide() even after the event loop is closed. We don't need the logic for quitting the event loop anymore at this point.
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn set_mouse_cursor(&self, cursor: MouseCursor) {
|
||||
|
@ -746,7 +759,7 @@ impl<Renderer: WinitCompatibleRenderer + 'static> WindowAdapterSealed for GLWind
|
|||
|
||||
impl<Renderer: WinitCompatibleRenderer + 'static> Drop for GLWindow<Renderer> {
|
||||
fn drop(&mut self) {
|
||||
self.unmap();
|
||||
self.unmap().expect("winit backend: error unmapping window");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,14 +31,17 @@ mod renderer {
|
|||
&self,
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
#[cfg(target_arch = "wasm32")] canvas_id: &str,
|
||||
) -> Rc<winit::window::Window>;
|
||||
fn hide(&self);
|
||||
) -> Result<Rc<winit::window::Window>, i_slint_core::platform::PlatformError>;
|
||||
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
|
||||
fn render(&self, size: PhysicalSize);
|
||||
fn render(&self, size: PhysicalSize) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
|
||||
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
|
||||
|
||||
fn resize_event(&self, size: PhysicalSize);
|
||||
fn resize_event(
|
||||
&self,
|
||||
size: PhysicalSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn html_canvas_element(&self) -> web_sys::HtmlCanvasElement;
|
||||
|
|
|
@ -10,6 +10,7 @@ use i_slint_core::api::{
|
|||
SetRenderingNotifierError,
|
||||
};
|
||||
use i_slint_core::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor};
|
||||
use i_slint_core::platform::PlatformError;
|
||||
use i_slint_core::renderer::Renderer;
|
||||
use i_slint_core::window::WindowAdapter;
|
||||
use i_slint_renderer_femtovg::FemtoVGRenderer;
|
||||
|
@ -27,25 +28,27 @@ impl GlutinFemtoVGRenderer {
|
|||
fn with_graphics_api(
|
||||
opengl_context: &glcontext::OpenGLContext,
|
||||
callback: impl FnOnce(i_slint_core::api::GraphicsAPI<'_>),
|
||||
) {
|
||||
opengl_context.ensure_current();
|
||||
) -> Result<(), PlatformError> {
|
||||
opengl_context.ensure_current()?;
|
||||
let api = GraphicsAPI::NativeOpenGL {
|
||||
get_proc_address: &|name| opengl_context.get_proc_address(name),
|
||||
};
|
||||
callback(api)
|
||||
callback(api);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn with_graphics_api(
|
||||
opengl_context: &glcontext::OpenGLContext,
|
||||
callback: impl FnOnce(i_slint_core::api::GraphicsAPI<'_>),
|
||||
) {
|
||||
) -> Result<(), PlatformError> {
|
||||
let canvas_element_id = opengl_context.html_canvas_element().id();
|
||||
let api = GraphicsAPI::WebGL {
|
||||
canvas_element_id: canvas_element_id.as_str(),
|
||||
context_type: "webgl",
|
||||
};
|
||||
callback(api)
|
||||
callback(api);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +67,7 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
|
|||
&self,
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
#[cfg(target_arch = "wasm32")] canvas_id: &str,
|
||||
) -> Rc<winit::window::Window> {
|
||||
) -> Result<Rc<winit::window::Window>, PlatformError> {
|
||||
let (window, opengl_context) = crate::event_loop::with_window_target(|event_loop| {
|
||||
glcontext::OpenGLContext::new_context(
|
||||
window_builder,
|
||||
|
@ -72,8 +75,7 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
canvas_id,
|
||||
)
|
||||
})
|
||||
.expect("Unable to create OpenGL context");
|
||||
})?;
|
||||
|
||||
self.renderer.show(
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -85,36 +87,37 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
|
|||
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
|
||||
Self::with_graphics_api(&opengl_context, |api| {
|
||||
callback.notify(RenderingState::RenderingSetup, &api)
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
*self.opengl_context.borrow_mut() = Some(opengl_context);
|
||||
|
||||
Rc::new(window)
|
||||
Ok(Rc::new(window))
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), PlatformError> {
|
||||
if let Some(opengl_context) = self.opengl_context.borrow_mut().take() {
|
||||
opengl_context.ensure_current();
|
||||
opengl_context.ensure_current()?;
|
||||
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
|
||||
Self::with_graphics_api(&opengl_context, |api| {
|
||||
callback.notify(RenderingState::RenderingTeardown, &api)
|
||||
});
|
||||
})?;
|
||||
}
|
||||
self.renderer.hide();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&self, size: PhysicalWindowSize) {
|
||||
fn render(&self, size: PhysicalWindowSize) -> Result<(), PlatformError> {
|
||||
let opengl_context = if self.opengl_context.borrow().is_some() {
|
||||
std::cell::Ref::map(self.opengl_context.borrow(), |context_opt| {
|
||||
context_opt.as_ref().unwrap()
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
opengl_context.ensure_current();
|
||||
opengl_context.ensure_current()?;
|
||||
|
||||
self.renderer.render(
|
||||
size,
|
||||
|
@ -122,31 +125,31 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
|
|||
|| {
|
||||
Self::with_graphics_api(&opengl_context, |api| {
|
||||
notifier_fn.notify(RenderingState::BeforeRendering, &api)
|
||||
});
|
||||
})
|
||||
}
|
||||
}),
|
||||
);
|
||||
)?;
|
||||
|
||||
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
|
||||
Self::with_graphics_api(&opengl_context, |api| {
|
||||
callback.notify(RenderingState::AfterRendering, &api)
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
opengl_context.swap_buffers();
|
||||
opengl_context.swap_buffers()
|
||||
}
|
||||
|
||||
fn as_core_renderer(&self) -> &dyn Renderer {
|
||||
self
|
||||
}
|
||||
|
||||
fn resize_event(&self, size: PhysicalWindowSize) {
|
||||
fn resize_event(&self, size: PhysicalWindowSize) -> Result<(), PlatformError> {
|
||||
let opengl_context = if self.opengl_context.borrow().is_some() {
|
||||
std::cell::Ref::map(self.opengl_context.borrow(), |context_opt| {
|
||||
context_opt.as_ref().unwrap()
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
opengl_context.ensure_resized(size)
|
||||
|
@ -219,16 +222,16 @@ impl Renderer for GlutinFemtoVGRenderer {
|
|||
&self,
|
||||
component: i_slint_core::component::ComponentRef,
|
||||
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
|
||||
) {
|
||||
) -> Result<(), PlatformError> {
|
||||
let opengl_context = if self.opengl_context.borrow().is_some() {
|
||||
std::cell::Ref::map(self.opengl_context.borrow(), |context_opt| {
|
||||
context_opt.as_ref().unwrap()
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
opengl_context.ensure_current();
|
||||
opengl_context.ensure_current()?;
|
||||
self.renderer.free_graphics_resources(component, _items)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use glutin::{
|
|||
prelude::*,
|
||||
surface::{SurfaceAttributesBuilder, WindowSurface},
|
||||
};
|
||||
use i_slint_core::api::PhysicalSize;
|
||||
use i_slint_core::{api::PhysicalSize, platform::PlatformError};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
|
||||
|
@ -27,35 +27,52 @@ impl OpenGLContext {
|
|||
self.canvas.clone()
|
||||
}
|
||||
|
||||
pub fn ensure_current(&self) {
|
||||
pub fn ensure_current(&self) -> Result<(), PlatformError> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if !self.context.is_current() {
|
||||
self.context.make_current(&self.surface).unwrap();
|
||||
self.context.make_current(&self.surface).map_err(|glutin_error| -> PlatformError {
|
||||
format!("FemtoVG: Error making context current: {glutin_error}").into()
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn swap_buffers(&self) {
|
||||
pub fn swap_buffers(&self) -> Result<(), PlatformError> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.surface.swap_buffers(&self.context).unwrap();
|
||||
self.surface.swap_buffers(&self.context).map_err(|glutin_error| -> PlatformError {
|
||||
format!("FemtoVG: Error swapping buffers: {glutin_error}").into()
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_resized(&self, _size: PhysicalSize) {
|
||||
pub fn ensure_resized(&self, _size: PhysicalSize) -> Result<(), PlatformError> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.ensure_current();
|
||||
self.surface.resize(
|
||||
&self.context,
|
||||
_size.width.try_into().unwrap(),
|
||||
_size.height.try_into().unwrap(),
|
||||
);
|
||||
let width = _size.width.try_into().map_err(|_| {
|
||||
format!(
|
||||
"Attempting to create window surface with an invalid width: {}",
|
||||
_size.width
|
||||
)
|
||||
})?;
|
||||
let height = _size.height.try_into().map_err(|_| {
|
||||
format!(
|
||||
"Attempting to create window surface with an invalid height: {}",
|
||||
_size.height
|
||||
)
|
||||
})?;
|
||||
|
||||
self.ensure_current()?;
|
||||
self.surface.resize(&self.context, width, height);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_context<T>(
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
window_target: &winit::event_loop::EventLoopWindowTarget<T>,
|
||||
) -> Result<(winit::window::Window, Self), Box<dyn std::error::Error>> {
|
||||
) -> Result<(winit::window::Window, Self), PlatformError> {
|
||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new();
|
||||
|
||||
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
|
||||
|
@ -73,6 +90,9 @@ impl OpenGLContext {
|
|||
}
|
||||
})
|
||||
.expect("internal error: Could not find any matching GL configuration")
|
||||
})
|
||||
.map_err(|glutin_err| {
|
||||
format!("Error creating OpenGL display with glutin: {}", glutin_err)
|
||||
})?;
|
||||
|
||||
let gl_display = gl_config.display();
|
||||
|
@ -91,20 +111,28 @@ impl OpenGLContext {
|
|||
let not_current_gl_context = unsafe {
|
||||
gl_display
|
||||
.create_context(&gl_config, &gles_context_attributes)
|
||||
.or_else(|_| gl_display.create_context(&gl_config, &fallback_context_attributes))?
|
||||
.or_else(|_| gl_display.create_context(&gl_config, &fallback_context_attributes))
|
||||
.map_err(|glutin_err| format!("Cannot create OpenGL context: {}", glutin_err))?
|
||||
};
|
||||
|
||||
let window = match window {
|
||||
Some(window) => window,
|
||||
None => glutin_winit::finalize_window(window_target, window_builder, &gl_config)?,
|
||||
None => glutin_winit::finalize_window(window_target, window_builder, &gl_config)
|
||||
.map_err(|winit_os_error| {
|
||||
format!("Error finalizing window for OpenGL rendering: {}", winit_os_error)
|
||||
})?,
|
||||
};
|
||||
|
||||
let size: winit::dpi::PhysicalSize<u32> = window.inner_size();
|
||||
|
||||
let width: std::num::NonZeroU32 =
|
||||
size.width.try_into().expect("new context called with zero width window");
|
||||
let height: std::num::NonZeroU32 =
|
||||
size.height.try_into().expect("new context called with zero height window");
|
||||
let width: std::num::NonZeroU32 = size.width.try_into().map_err(|e| {
|
||||
format!("Attempting to create window surface with width that doesn't fit into U32: {e}")
|
||||
})?;
|
||||
let height: std::num::NonZeroU32 = size.height.try_into().map_err(|e| {
|
||||
format!(
|
||||
"Attempting to create window surface with height that doesn't fit into U32: {e}"
|
||||
)
|
||||
})?;
|
||||
|
||||
let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
|
||||
window.raw_window_handle(),
|
||||
|
@ -112,7 +140,11 @@ impl OpenGLContext {
|
|||
height,
|
||||
);
|
||||
|
||||
let surface = unsafe { gl_display.create_window_surface(&gl_config, &attrs)? };
|
||||
let surface = unsafe {
|
||||
gl_display.create_window_surface(&gl_config, &attrs).map_err(|glutin_err| {
|
||||
format!("Error creating OpenGL Window surface: {}", glutin_err)
|
||||
})?
|
||||
};
|
||||
|
||||
// Align the GL layer to the top-left, so that resizing only invalidates the bottom/right
|
||||
// part of the window.
|
||||
|
@ -129,10 +161,13 @@ impl OpenGLContext {
|
|||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
window,
|
||||
Self { context: not_current_gl_context.make_current(&surface).unwrap(), surface },
|
||||
))
|
||||
let context = not_current_gl_context.make_current(&surface)
|
||||
.map_err(|glutin_error: glutin::error::Error| -> PlatformError {
|
||||
format!("FemtoVG Renderer: Failed to make newly created OpenGL context current: {glutin_error}")
|
||||
.into()
|
||||
})?;
|
||||
|
||||
Ok((window, Self { context, surface }))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -140,19 +175,28 @@ impl OpenGLContext {
|
|||
window_builder: winit::window::WindowBuilder,
|
||||
window_target: &winit::event_loop::EventLoopWindowTarget<T>,
|
||||
canvas_id: &str,
|
||||
) -> Result<(winit::window::Window, Self), Box<dyn std::error::Error>> {
|
||||
let window = window_builder.build(window_target)?;
|
||||
) -> Result<(winit::window::Window, Self), PlatformError> {
|
||||
let window = window_builder.build(window_target).map_err(|winit_os_err| {
|
||||
format!(
|
||||
"FemtoVG Renderer: Could not create winit window wrapper for DOM canvas: {}",
|
||||
winit_os_err
|
||||
)
|
||||
})?;
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let canvas = web_sys::window()
|
||||
.unwrap()
|
||||
.ok_or_else(|| "FemtoVG Renderer: Could not retrieve DOM window".to_string())?
|
||||
.document()
|
||||
.unwrap()
|
||||
.ok_or_else(|| "FemtoVG Renderer: Could not retrieve DOM document".to_string())?
|
||||
.get_element_by_id(canvas_id)
|
||||
.unwrap()
|
||||
.ok_or_else(|| {
|
||||
format!("FemtoVG Renderer: Could not retrieve existing HTML Canvas element '{canvas_id}'")
|
||||
})?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.unwrap();
|
||||
.map_err(|_| {
|
||||
format!("FemtoVG Renderer: Specified DOM element '{canvas_id}' is not a HTML Canvas")
|
||||
})?;
|
||||
|
||||
Ok((window, Self { canvas }))
|
||||
}
|
||||
|
|
|
@ -17,30 +17,39 @@ impl super::WinitCompatibleRenderer for SkiaRenderer {
|
|||
Self { renderer: i_slint_renderer_skia::SkiaRenderer::new(window_adapter_weak.clone()) }
|
||||
}
|
||||
|
||||
fn show(&self, window_builder: winit::window::WindowBuilder) -> Rc<winit::window::Window> {
|
||||
fn show(
|
||||
&self,
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
) -> Result<Rc<winit::window::Window>, i_slint_core::platform::PlatformError> {
|
||||
let window = Rc::new(crate::event_loop::with_window_target(|event_loop| {
|
||||
window_builder.build(event_loop.event_loop_target()).unwrap()
|
||||
}));
|
||||
|
||||
let size: winit::dpi::PhysicalSize<u32> = window.inner_size();
|
||||
self.renderer.show(window.clone(), PhysicalWindowSize::new(size.width, size.height));
|
||||
self.renderer.show(window.clone(), PhysicalWindowSize::new(size.width, size.height))?;
|
||||
|
||||
window
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
self.renderer.hide();
|
||||
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.renderer.hide()
|
||||
}
|
||||
|
||||
fn render(&self, size: PhysicalWindowSize) {
|
||||
self.renderer.render(size);
|
||||
fn render(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.renderer.render(size)
|
||||
}
|
||||
|
||||
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
|
||||
&self.renderer
|
||||
}
|
||||
|
||||
fn resize_event(&self, size: PhysicalWindowSize) {
|
||||
fn resize_event(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.renderer.resize_event(size)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,29 +28,43 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
fn show(&self, window_builder: winit::window::WindowBuilder) -> Rc<winit::window::Window> {
|
||||
let window = Rc::new(crate::event_loop::with_window_target(|event_loop| {
|
||||
window_builder.build(event_loop.event_loop_target()).unwrap()
|
||||
}));
|
||||
fn show(
|
||||
&self,
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
) -> Result<Rc<winit::window::Window>, i_slint_core::platform::PlatformError> {
|
||||
let window = crate::event_loop::with_window_target(|event_loop| {
|
||||
window_builder.build(event_loop.event_loop_target()).map_err(|winit_os_error| {
|
||||
format!("Error creating native window for software rendering: {}", winit_os_error)
|
||||
})
|
||||
})?;
|
||||
let window = Rc::new(window);
|
||||
|
||||
*self.canvas.borrow_mut() = Some(unsafe {
|
||||
softbuffer::GraphicsContext::new(window.as_ref(), window.as_ref()).unwrap()
|
||||
softbuffer::GraphicsContext::new(window.as_ref(), window.as_ref()).map_err(
|
||||
|softbuffer_error| {
|
||||
format!("Error creating softbuffer graphics context: {}", softbuffer_error)
|
||||
},
|
||||
)?
|
||||
});
|
||||
|
||||
window
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.canvas.borrow_mut().take();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&self, size: PhysicalWindowSize) {
|
||||
fn render(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let mut canvas = if self.canvas.borrow().is_some() {
|
||||
std::cell::RefMut::map(self.canvas.borrow_mut(), |canvas_opt| {
|
||||
canvas_opt.as_mut().unwrap()
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let width = size.width as usize;
|
||||
|
@ -95,9 +109,15 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
|
|||
);
|
||||
|
||||
canvas.set_buffer(&softbuffer_buffer, width as u16, height as u16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_event(&self, _size: PhysicalWindowSize) {}
|
||||
fn resize_event(
|
||||
&self,
|
||||
_size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
|
||||
&self.renderer
|
||||
|
|
|
@ -381,7 +381,7 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
|
|||
fn run(&self) -> core::result::Result<(), slint::PlatformError> {
|
||||
self.show()?;
|
||||
slint::run_event_loop()?;
|
||||
self.hide();
|
||||
self.hide()?;
|
||||
core::result::Result::Ok(())
|
||||
}
|
||||
|
||||
|
@ -389,7 +389,7 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
|
|||
self.window().show()
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> core::result::Result<(), slint::PlatformError> {
|
||||
self.window().hide()
|
||||
}
|
||||
|
||||
|
|
|
@ -348,8 +348,8 @@ impl Window {
|
|||
}
|
||||
|
||||
/// De-registers the window from the windowing system, therefore hiding it.
|
||||
pub fn hide(&self) {
|
||||
self.0.hide();
|
||||
pub fn hide(&self) -> Result<(), PlatformError> {
|
||||
self.0.hide()
|
||||
}
|
||||
|
||||
/// This function allows registering a callback that's invoked during the different phases of
|
||||
|
@ -552,7 +552,7 @@ pub trait ComponentHandle {
|
|||
|
||||
/// Marks the window of this component to be hidden on the screen. This de-registers
|
||||
/// the window from the windowing system and it will not receive any further events.
|
||||
fn hide(&self);
|
||||
fn hide(&self) -> Result<(), PlatformError>;
|
||||
|
||||
/// Returns the Window associated with this component. The window API can be used
|
||||
/// to control different aspects of the integration into the windowing system,
|
||||
|
|
|
@ -142,7 +142,7 @@ pub fn unregister_component<Base>(
|
|||
window_adapter.renderer().free_graphics_resources(
|
||||
component,
|
||||
&mut item_array.iter().map(|item| item.apply_pin(base)),
|
||||
);
|
||||
).expect("Fatal error encountered when freeing graphics resources while destroying Slint component");
|
||||
window_adapter
|
||||
.unregister_component(component, &mut item_array.iter().map(|item| item.apply_pin(base)));
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ pub trait Renderer {
|
|||
&self,
|
||||
_component: ComponentRef,
|
||||
_items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
|
||||
) {
|
||||
) -> Result<(), crate::platform::PlatformError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark a given region as dirty regardless whether the items actually are dirty.
|
||||
|
|
|
@ -302,13 +302,14 @@ impl Renderer for SoftwareRenderer {
|
|||
&self,
|
||||
_component: crate::component::ComponentRef,
|
||||
items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
|
||||
) {
|
||||
) -> Result<(), crate::platform::PlatformError> {
|
||||
for item in items {
|
||||
item.cached_rendering_data_offset().release(&mut self.partial_cache.borrow_mut());
|
||||
}
|
||||
// We don't have a way to determine the screen region of the delete items, what's in the cache is relative. So
|
||||
// as a last resort, refresh everything.
|
||||
self.force_screen_refresh.set(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_dirty_region(&self, region: crate::item_rendering::DirtyRegion) {
|
||||
|
|
|
@ -72,7 +72,9 @@ pub trait WindowAdapterSealed {
|
|||
Ok(())
|
||||
}
|
||||
/// De-registers the window from the windowing system.
|
||||
fn hide(&self) {}
|
||||
fn hide(&self) -> Result<(), PlatformError> {
|
||||
Ok(())
|
||||
}
|
||||
/// Issue a request to the windowing system to re-render the contents of the window. This is typically an asynchronous
|
||||
/// request.
|
||||
fn request_redraw(&self) {}
|
||||
|
@ -591,7 +593,10 @@ impl WindowInner {
|
|||
|
||||
/// Calls the render_components to render the main component and any sub-window components, tracked by a
|
||||
/// property dependency tracker.
|
||||
pub fn draw_contents(&self, render_components: impl FnOnce(&[(&ComponentRc, LogicalPoint)])) {
|
||||
pub fn draw_contents<T>(
|
||||
&self,
|
||||
render_components: impl FnOnce(&[(&ComponentRc, LogicalPoint)]) -> T,
|
||||
) -> T {
|
||||
let draw_fn = || {
|
||||
let component_rc = self.component();
|
||||
|
||||
|
@ -609,7 +614,7 @@ impl WindowInner {
|
|||
(&popup_component, popup_coordinates),
|
||||
])
|
||||
} else {
|
||||
render_components(&[(&component_rc, LogicalPoint::default())]);
|
||||
render_components(&[(&component_rc, LogicalPoint::default())])
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -625,8 +630,8 @@ impl WindowInner {
|
|||
}
|
||||
|
||||
/// De-registers the window with the windowing system.
|
||||
pub fn hide(&self) {
|
||||
self.window_adapter().hide();
|
||||
pub fn hide(&self) -> Result<(), PlatformError> {
|
||||
self.window_adapter().hide()
|
||||
}
|
||||
|
||||
/// Show a popup at the given position relative to the item
|
||||
|
@ -833,7 +838,7 @@ pub mod ffi {
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_windowrc_hide(handle: *const WindowAdapterRcOpaque) {
|
||||
let window = &*(handle as *const Rc<dyn WindowAdapter>);
|
||||
window.hide();
|
||||
window.hide().unwrap();
|
||||
}
|
||||
|
||||
/// Returns the visibility state of the window. This function can return false even if you previously called show()
|
||||
|
|
|
@ -1045,17 +1045,16 @@ impl ComponentHandle for ComponentInstance {
|
|||
comp.borrow_instance().window_adapter().window().show()
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
fn hide(&self) -> Result<(), PlatformError> {
|
||||
generativity::make_guard!(guard);
|
||||
let comp = self.inner.unerase(guard);
|
||||
comp.borrow_instance().window_adapter().window().hide();
|
||||
comp.borrow_instance().window_adapter().window().hide()
|
||||
}
|
||||
|
||||
fn run(&self) -> Result<(), PlatformError> {
|
||||
self.show()?;
|
||||
run_event_loop()?;
|
||||
self.hide();
|
||||
Ok(())
|
||||
self.hide()
|
||||
}
|
||||
|
||||
fn window(&self) -> &Window {
|
||||
|
|
|
@ -562,7 +562,7 @@ pub extern "C" fn slint_interpreter_component_instance_show(
|
|||
if is_visible {
|
||||
comp.borrow_instance().window_adapter().show().unwrap();
|
||||
} else {
|
||||
comp.borrow_instance().window_adapter().hide();
|
||||
comp.borrow_instance().window_adapter().hide().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ use i_slint_core::items::Item;
|
|||
use i_slint_core::lengths::{
|
||||
LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
|
||||
};
|
||||
use i_slint_core::platform::PlatformError;
|
||||
use i_slint_core::renderer::Renderer;
|
||||
use i_slint_core::window::{WindowAdapter, WindowInner};
|
||||
use i_slint_core::Brush;
|
||||
|
@ -114,21 +115,21 @@ impl FemtoVGRenderer {
|
|||
pub fn render(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
mut before_rendering_callback: Option<impl FnOnce()>,
|
||||
) {
|
||||
mut before_rendering_callback: Option<impl FnOnce() -> Result<(), PlatformError>>,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
|
||||
let canvas = if self.canvas.borrow().is_some() {
|
||||
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
|
||||
} else {
|
||||
return;
|
||||
return Err(format!("FemtoVG renderer: render() called before show()").into());
|
||||
};
|
||||
|
||||
let window_adapter = self.window_adapter_weak.upgrade().unwrap();
|
||||
let window = WindowInner::from_pub(window_adapter.window());
|
||||
|
||||
window.draw_contents(|components| {
|
||||
window.draw_contents(|components| -> Result<(), PlatformError> {
|
||||
let window_background_brush = window.window_item().map(|w| w.as_pin_ref().background());
|
||||
|
||||
{
|
||||
|
@ -161,7 +162,7 @@ impl FemtoVGRenderer {
|
|||
femtovg_canvas.set_size(width, height, 1.0);
|
||||
drop(femtovg_canvas);
|
||||
|
||||
callback();
|
||||
callback()?;
|
||||
}
|
||||
|
||||
let window_adapter = self.window_adapter_weak.upgrade().unwrap();
|
||||
|
@ -201,7 +202,8 @@ impl FemtoVGRenderer {
|
|||
// avoid GPU memory leaks.
|
||||
canvas.texture_cache.borrow_mut().drain();
|
||||
drop(item_renderer);
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,14 +391,15 @@ impl Renderer for FemtoVGRenderer {
|
|||
&self,
|
||||
component: i_slint_core::component::ComponentRef,
|
||||
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
|
||||
) {
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let canvas = if self.canvas.borrow().is_some() {
|
||||
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
canvas.graphics_cache.component_destroyed(component);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -224,7 +224,11 @@ impl SwapChain {
|
|||
[make_surface(0), make_surface(1)]
|
||||
}
|
||||
|
||||
fn resize(&mut self, width: u32, height: u32) {
|
||||
fn resize(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.gr_context.flush_submit_and_sync_cpu();
|
||||
|
||||
self.wait_for_buffer(0);
|
||||
|
@ -236,7 +240,9 @@ impl SwapChain {
|
|||
let resize_result =
|
||||
self.swap_chain.ResizeBuffers(0, width, height, DEFAULT_SURFACE_FORMAT, 0);
|
||||
if resize_result != S_OK {
|
||||
panic!("Error resizing swap chain buffers: {:x}", resize_result);
|
||||
return Err(
|
||||
format!("Error resizing swap chain buffers: {:x}", resize_result).into()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,6 +252,7 @@ impl SwapChain {
|
|||
width as i32,
|
||||
height as i32,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_for_buffer(&mut self, buffer_index: usize) {
|
||||
|
@ -281,7 +288,7 @@ impl super::Surface for D3DSurface {
|
|||
window: &dyn raw_window_handle::HasRawWindowHandle,
|
||||
_display: &dyn raw_window_handle::HasRawDisplayHandle,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Self {
|
||||
) -> Result<Self, i_slint_core::platform::PlatformError> {
|
||||
let factory_flags = 0;
|
||||
/*
|
||||
let factory_flags = dxgi1_3::DXGI_CREATE_FACTORY_DEBUG;
|
||||
|
@ -401,7 +408,7 @@ impl super::Surface for D3DSurface {
|
|||
let swap_chain =
|
||||
RefCell::new(SwapChain::new(queue, &device, gr_context, &window, size, &dxgi_factory));
|
||||
|
||||
Self { swap_chain }
|
||||
Ok(Self { swap_chain })
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
@ -412,26 +419,34 @@ impl super::Surface for D3DSurface {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
fn resize_event(&self, size: PhysicalWindowSize) {
|
||||
self.swap_chain.borrow_mut().resize(size.width, size.height);
|
||||
fn resize_event(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.swap_chain.borrow_mut().resize(size.width, size.height)
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
_size: PhysicalWindowSize,
|
||||
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
|
||||
) {
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.swap_chain
|
||||
.borrow_mut()
|
||||
.render_and_present(|surface, gr_context| callback(surface.canvas(), gr_context))
|
||||
.render_and_present(|surface, gr_context| callback(surface.canvas(), gr_context));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bits_per_pixel(&self) -> u8 {
|
||||
fn bits_per_pixel(&self) -> Result<u8, i_slint_core::platform::PlatformError> {
|
||||
let mut desc = dxgi::DXGI_SWAP_CHAIN_DESC::default();
|
||||
unsafe { self.swap_chain.borrow().swap_chain.GetDesc(&mut desc) };
|
||||
match desc.BufferDesc.Format {
|
||||
Ok(match desc.BufferDesc.Format {
|
||||
DEFAULT_SURFACE_FORMAT => 32,
|
||||
_ => 0, // Not mapped yet
|
||||
}
|
||||
fmt @ _ => {
|
||||
return Err(
|
||||
format!("Skia D3D Renderer: Unsupported buffer format found {fmt:?}").into()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use i_slint_core::items::Item;
|
|||
use i_slint_core::lengths::{
|
||||
LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
|
||||
};
|
||||
use i_slint_core::platform::PlatformError;
|
||||
use i_slint_core::window::{WindowAdapter, WindowInner};
|
||||
use i_slint_core::Brush;
|
||||
|
||||
|
@ -70,15 +71,19 @@ impl<
|
|||
|
||||
/// Use the provided window and display for rendering the Slint scene in future calls to [`Self::render()`].
|
||||
/// The size must be identical to the size of the window in physical pixels that is providing the window handle.
|
||||
pub fn show(&self, native_window: NativeWindowWrapper, size: PhysicalWindowSize) {
|
||||
let surface = DefaultSurface::new(&native_window, &native_window, size);
|
||||
pub fn show(
|
||||
&self,
|
||||
native_window: NativeWindowWrapper,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), PlatformError> {
|
||||
let surface = DefaultSurface::new(&native_window, &native_window, size)?;
|
||||
|
||||
let rendering_metrics_collector = RenderingMetricsCollector::new(
|
||||
self.window_adapter_weak.clone(),
|
||||
&format!(
|
||||
"Skia renderer (skia backend {}; surface: {} bpp)",
|
||||
surface.name(),
|
||||
surface.bits_per_pixel()
|
||||
surface.bits_per_pixel()?
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -95,11 +100,13 @@ impl<
|
|||
}
|
||||
|
||||
*self.canvas.borrow_mut() = Some(canvas);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Release any graphics resources and disconnect the rendere from a window that it was previously associated when when
|
||||
/// calling [`Self::show()]`.
|
||||
pub fn hide(&self) {
|
||||
pub fn hide(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
if let Some(canvas) = self.canvas.borrow_mut().take() {
|
||||
canvas.surface.with_active_surface(|| {
|
||||
if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
|
||||
|
@ -107,16 +114,20 @@ impl<
|
|||
callback.notify(RenderingState::RenderingTeardown, &api)
|
||||
})
|
||||
}
|
||||
});
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the scene in the previously associated window. The size parameter must match the size of the window.
|
||||
pub fn render(&self, size: PhysicalWindowSize) {
|
||||
pub fn render(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let canvas = if self.canvas.borrow().is_some() {
|
||||
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
|
||||
} else {
|
||||
return;
|
||||
return Err(format!("Skia renderer: render() called before show()").into());
|
||||
};
|
||||
|
||||
let window_adapter = self.window_adapter_weak.upgrade().unwrap();
|
||||
|
@ -185,15 +196,18 @@ impl<
|
|||
canvas
|
||||
.with_graphics_api(|api| callback.notify(RenderingState::AfterRendering, &api))
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/// Call this when you receive a notification from the windowing system that the size of the window has changed.
|
||||
pub fn resize_event(&self, size: PhysicalWindowSize) {
|
||||
pub fn resize_event(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let canvas = if self.canvas.borrow().is_some() {
|
||||
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
canvas.surface.resize_event(size)
|
||||
|
@ -372,15 +386,16 @@ impl<NativeWindowWrapper> i_slint_core::renderer::Renderer for SkiaRenderer<Nati
|
|||
&self,
|
||||
component: i_slint_core::component::ComponentRef,
|
||||
_items: &mut dyn Iterator<Item = std::pin::Pin<i_slint_core::items::ItemRef<'_>>>,
|
||||
) {
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
let canvas = if self.canvas.borrow().is_some() {
|
||||
std::cell::Ref::map(self.canvas.borrow(), |canvas_opt| canvas_opt.as_ref().unwrap())
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
canvas.image_cache.component_destroyed(component);
|
||||
canvas.path_cache.component_destroyed(component);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,19 +405,28 @@ trait Surface {
|
|||
window: &dyn raw_window_handle::HasRawWindowHandle,
|
||||
display: &dyn raw_window_handle::HasRawDisplayHandle,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Self;
|
||||
) -> Result<Self, PlatformError>
|
||||
where
|
||||
Self: Sized;
|
||||
fn name(&self) -> &'static str;
|
||||
fn with_graphics_api(&self, callback: impl FnOnce(GraphicsAPI<'_>));
|
||||
fn with_active_surface(&self, callback: impl FnOnce()) {
|
||||
callback()
|
||||
fn with_active_surface(
|
||||
&self,
|
||||
callback: impl FnOnce(),
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
callback();
|
||||
Ok(())
|
||||
}
|
||||
fn render(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
|
||||
);
|
||||
fn resize_event(&self, size: PhysicalWindowSize);
|
||||
fn bits_per_pixel(&self) -> u8;
|
||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
fn resize_event(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError>;
|
||||
fn bits_per_pixel(&self) -> Result<u8, PlatformError>;
|
||||
}
|
||||
|
||||
struct SkiaCanvas<SurfaceType: Surface, NativeWindowWrapper> {
|
||||
|
|
|
@ -25,8 +25,9 @@ impl super::Surface for MetalSurface {
|
|||
window: &dyn raw_window_handle::HasRawWindowHandle,
|
||||
_display: &dyn raw_window_handle::HasRawDisplayHandle,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Self {
|
||||
let device = metal::Device::system_default().expect("no metal device found");
|
||||
) -> Result<Self, i_slint_core::platform::PlatformError> {
|
||||
let device = metal::Device::system_default()
|
||||
.ok_or_else(|| format!("Skia Renderer: No metal device found"))?;
|
||||
|
||||
let layer = metal::MetalLayer::new();
|
||||
layer.set_device(&device);
|
||||
|
@ -40,7 +41,9 @@ impl super::Surface for MetalSurface {
|
|||
raw_window_handle::RawWindowHandle::AppKit(
|
||||
raw_window_handle::AppKitWindowHandle { ns_view, .. },
|
||||
) => ns_view,
|
||||
_ => panic!("Metal surface is only supported with AppKit"),
|
||||
_ => {
|
||||
return Err("Skia Renderer: Metal surface is only supported with AppKit".into())
|
||||
}
|
||||
} as cocoa_id;
|
||||
view.setWantsLayer(YES);
|
||||
view.setLayer(layer.as_ref() as *const _ as _);
|
||||
|
@ -58,7 +61,7 @@ impl super::Surface for MetalSurface {
|
|||
|
||||
let gr_context = skia_safe::gpu::DirectContext::new_metal(&backend, None).unwrap().into();
|
||||
|
||||
Self { command_queue, layer, gr_context }
|
||||
Ok(Self { command_queue, layer, gr_context })
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
@ -69,19 +72,28 @@ impl super::Surface for MetalSurface {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
fn resize_event(&self, size: PhysicalWindowSize) {
|
||||
fn resize_event(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
self.layer.set_drawable_size(CGSize::new(size.width as f64, size.height as f64));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
_size: PhysicalWindowSize,
|
||||
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
|
||||
) {
|
||||
) -> Result<(), i_slint_core::platform::PlatformError> {
|
||||
autoreleasepool(|| {
|
||||
let drawable = match self.layer.next_drawable() {
|
||||
Some(drawable) => drawable,
|
||||
None => return,
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Skia Metal Renderer: Failed to retrieve next drawable for rendering"
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let gr_context = &mut self.gr_context.borrow_mut();
|
||||
|
@ -118,15 +130,17 @@ impl super::Surface for MetalSurface {
|
|||
let command_buffer = self.command_queue.new_command_buffer();
|
||||
command_buffer.present_drawable(drawable);
|
||||
command_buffer.commit();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn bits_per_pixel(&self) -> u8 {
|
||||
fn bits_per_pixel(&self) -> Result<u8, i_slint_core::platform::PlatformError> {
|
||||
// From https://developer.apple.com/documentation/metal/mtlpixelformat:
|
||||
// The storage size of each pixel format is determined by the sum of its components.
|
||||
// For example, the storage size of BGRA8Unorm is 32 bits (four 8-bit components) and
|
||||
// the storage size of BGR5A1Unorm is 16 bits (three 5-bit components and one 1-bit component).
|
||||
match self.layer.pixel_format() {
|
||||
Ok(match self.layer.pixel_format() {
|
||||
MTLPixelFormat::B5G6R5Unorm
|
||||
| MTLPixelFormat::A1BGR5Unorm
|
||||
| MTLPixelFormat::ABGR4Unorm
|
||||
|
@ -146,7 +160,12 @@ impl super::Surface for MetalSurface {
|
|||
| MTLPixelFormat::RGBA16Uint
|
||||
| MTLPixelFormat::RGBA16Sint => 64,
|
||||
MTLPixelFormat::RGBA32Uint | MTLPixelFormat::RGBA32Sint => 128,
|
||||
_ => 0, // Not mapped yet
|
||||
}
|
||||
fmt @ _ => {
|
||||
return Err(format!(
|
||||
"Skia Metal Renderer: Unsupported layer pixel format found {fmt:?}"
|
||||
)
|
||||
.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, num::NonZeroU32};
|
||||
|
||||
use glutin::{
|
||||
config::GetGlConfig,
|
||||
|
@ -10,8 +10,8 @@ use glutin::{
|
|||
prelude::*,
|
||||
surface::{SurfaceAttributesBuilder, WindowSurface},
|
||||
};
|
||||
use i_slint_core::api::GraphicsAPI;
|
||||
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
|
||||
use i_slint_core::{api::GraphicsAPI, platform::PlatformError};
|
||||
|
||||
pub struct OpenGLSurface {
|
||||
fb_info: skia_safe::gpu::gl::FramebufferInfo,
|
||||
|
@ -28,8 +28,16 @@ impl super::Surface for OpenGLSurface {
|
|||
window: &dyn raw_window_handle::HasRawWindowHandle,
|
||||
display: &dyn raw_window_handle::HasRawDisplayHandle,
|
||||
size: PhysicalWindowSize,
|
||||
) -> Self {
|
||||
let (current_glutin_context, glutin_surface) = Self::init_glutin(window, display, size);
|
||||
) -> Result<Self, PlatformError> {
|
||||
let width: std::num::NonZeroU32 = size.width.try_into().map_err(|_| {
|
||||
format!("Attempting to create window surface with an invalid width: {}", size.width)
|
||||
})?;
|
||||
let height: std::num::NonZeroU32 = size.height.try_into().map_err(|_| {
|
||||
format!("Attempting to create window surface with an invalid height: {}", size.height)
|
||||
})?;
|
||||
|
||||
let (current_glutin_context, glutin_surface) =
|
||||
Self::init_glutin(window, display, width, height)?;
|
||||
|
||||
let fb_info = {
|
||||
use glow::HasContext;
|
||||
|
@ -42,7 +50,9 @@ impl super::Surface for OpenGLSurface {
|
|||
let fboid = unsafe { gl.get_parameter_i32(glow::FRAMEBUFFER_BINDING) };
|
||||
|
||||
skia_safe::gpu::gl::FramebufferInfo {
|
||||
fboid: fboid.try_into().unwrap(),
|
||||
fboid: fboid.try_into().map_err(|_| {
|
||||
format!("Skia Renderer: Internal error, framebuffer binding returned signed id")
|
||||
})?,
|
||||
format: skia_safe::gpu::gl::Format::RGBA8.into(),
|
||||
}
|
||||
};
|
||||
|
@ -51,19 +61,36 @@ impl super::Surface for OpenGLSurface {
|
|||
current_glutin_context.display().get_proc_address(name) as *const _
|
||||
});
|
||||
|
||||
let mut gr_context = skia_safe::gpu::DirectContext::new_gl(gl_interface, None).unwrap();
|
||||
let mut gr_context =
|
||||
skia_safe::gpu::DirectContext::new_gl(gl_interface, None).ok_or_else(|| {
|
||||
format!("Skia Renderer: Internal Error: Could not create Skia OpenGL interface")
|
||||
})?;
|
||||
|
||||
let surface =
|
||||
Self::create_internal_surface(fb_info, ¤t_glutin_context, &mut gr_context, size)
|
||||
.into();
|
||||
let width: i32 = size.width.try_into().map_err(|e| {
|
||||
format!("Attempting to create window surface with width that doesn't fit into non-zero i32: {e}")
|
||||
})?;
|
||||
let height: i32 = size.height.try_into().map_err(|e| {
|
||||
format!(
|
||||
"Attempting to create window surface with height that doesn't fit into non-zero i32: {e}"
|
||||
)
|
||||
})?;
|
||||
|
||||
Self {
|
||||
let surface = Self::create_internal_surface(
|
||||
fb_info,
|
||||
¤t_glutin_context,
|
||||
&mut gr_context,
|
||||
width,
|
||||
height,
|
||||
)?
|
||||
.into();
|
||||
|
||||
Ok(Self {
|
||||
fb_info,
|
||||
surface,
|
||||
gr_context: RefCell::new(gr_context),
|
||||
glutin_context: current_glutin_context,
|
||||
glutin_surface,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
@ -79,57 +106,79 @@ impl super::Surface for OpenGLSurface {
|
|||
callback(api)
|
||||
}
|
||||
|
||||
fn with_active_surface(&self, callback: impl FnOnce()) {
|
||||
self.ensure_context_current();
|
||||
fn with_active_surface(&self, callback: impl FnOnce()) -> Result<(), PlatformError> {
|
||||
self.ensure_context_current()?;
|
||||
callback();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
size: PhysicalWindowSize,
|
||||
callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext),
|
||||
) {
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
|
||||
self.ensure_context_current();
|
||||
) -> Result<(), PlatformError> {
|
||||
self.ensure_context_current()?;
|
||||
|
||||
let current_context = &self.glutin_context;
|
||||
|
||||
let gr_context = &mut self.gr_context.borrow_mut();
|
||||
|
||||
let mut surface = self.surface.borrow_mut();
|
||||
if width != surface.width() as u32 || height != surface.height() as u32 {
|
||||
*surface =
|
||||
Self::create_internal_surface(self.fb_info, ¤t_context, gr_context, size);
|
||||
|
||||
let width = size.width.try_into().ok();
|
||||
let height = size.height.try_into().ok();
|
||||
|
||||
if let Some((width, height)) = width.zip(height) {
|
||||
if width != surface.width() || height != surface.height() {
|
||||
*surface = Self::create_internal_surface(
|
||||
self.fb_info,
|
||||
¤t_context,
|
||||
gr_context,
|
||||
width,
|
||||
height,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let skia_canvas = surface.canvas();
|
||||
|
||||
callback(skia_canvas, gr_context);
|
||||
|
||||
self.glutin_surface.swap_buffers(¤t_context).unwrap();
|
||||
self.glutin_surface.swap_buffers(¤t_context).map_err(|glutin_error| {
|
||||
format!("Skia OpenGL Renderer: Error swapping buffers: {glutin_error}").into()
|
||||
})
|
||||
}
|
||||
|
||||
fn resize_event(&self, size: PhysicalWindowSize) {
|
||||
self.ensure_context_current();
|
||||
fn resize_event(&self, size: PhysicalWindowSize) -> Result<(), PlatformError> {
|
||||
self.ensure_context_current()?;
|
||||
|
||||
self.glutin_surface.resize(
|
||||
&self.glutin_context,
|
||||
size.width.try_into().unwrap(),
|
||||
size.height.try_into().unwrap(),
|
||||
);
|
||||
let width = size.width.try_into().map_err(|e| {
|
||||
format!("Skia: Attempting to resize window surface with width that doesn't fit into U32: {e}")
|
||||
})?;
|
||||
let height = size.height.try_into().map_err(|e| {
|
||||
format!(
|
||||
"Skia: Attempting to resize window surface with height that doesn't fit into U32: {e}"
|
||||
)
|
||||
})?;
|
||||
|
||||
self.glutin_surface.resize(&self.glutin_context, width, height);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bits_per_pixel(&self) -> u8 {
|
||||
fn bits_per_pixel(&self) -> Result<u8, PlatformError> {
|
||||
let config = self.glutin_context.config();
|
||||
let rgb_bits = match config.color_buffer_type() {
|
||||
Some(glutin::config::ColorBufferType::Rgb { r_size, g_size, b_size }) => {
|
||||
r_size + g_size + b_size
|
||||
}
|
||||
_ => panic!("unsupported color buffer used with Skia OpenGL renderer"),
|
||||
other @ _ => {
|
||||
return Err(format!(
|
||||
"Skia OpenGL Renderer: unsupported color buffer {other:?} encountered"
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
rgb_bits + config.alpha_size()
|
||||
Ok(rgb_bits + config.alpha_size())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,11 +186,15 @@ impl OpenGLSurface {
|
|||
fn init_glutin(
|
||||
_window: &dyn raw_window_handle::HasRawWindowHandle,
|
||||
_display: &dyn raw_window_handle::HasRawDisplayHandle,
|
||||
_size: PhysicalWindowSize,
|
||||
) -> (
|
||||
glutin::context::PossiblyCurrentContext,
|
||||
glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||
) {
|
||||
width: NonZeroU32,
|
||||
height: NonZeroU32,
|
||||
) -> Result<
|
||||
(
|
||||
glutin::context::PossiblyCurrentContext,
|
||||
glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||
),
|
||||
PlatformError,
|
||||
> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
let prefs = [glutin::display::DisplayApiPreference::Cgl];
|
||||
|
@ -154,11 +207,6 @@ impl OpenGLSurface {
|
|||
}
|
||||
}
|
||||
|
||||
let width: std::num::NonZeroU32 =
|
||||
_size.width.try_into().expect("new context called with zero width window");
|
||||
let height: std::num::NonZeroU32 =
|
||||
_size.height.try_into().expect("new context called with zero height window");
|
||||
|
||||
let try_create_surface =
|
||||
|display_api_preference| -> Result<(_, _), Box<dyn std::error::Error>> {
|
||||
let gl_display = unsafe {
|
||||
|
@ -229,16 +277,16 @@ impl OpenGLSurface {
|
|||
let is_last = i == num_prefs - 1;
|
||||
|
||||
match try_create_surface(pref) {
|
||||
Ok(result) => Some(result),
|
||||
Ok(result) => Some(Ok(result)),
|
||||
Err(glutin_error) => {
|
||||
if is_last {
|
||||
panic!("Glutin error creating GL surface: {}", glutin_error);
|
||||
return Some(Err(format!("Skia OpenGL Renderer: Failed to create OpenGL Window Surface: {glutin_error}")));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
.unwrap()?;
|
||||
|
||||
// Align the GL layer to the top-left, so that resizing only invalidates the bottom/right
|
||||
// part of the window.
|
||||
|
@ -255,44 +303,61 @@ impl OpenGLSurface {
|
|||
}
|
||||
}
|
||||
|
||||
(not_current_gl_context.make_current(&surface).unwrap(), surface)
|
||||
let context = not_current_gl_context.make_current(&surface)
|
||||
.map_err(|glutin_error: glutin::error::Error| -> PlatformError {
|
||||
format!("FemtoVG Renderer: Failed to make newly created OpenGL context current: {glutin_error}")
|
||||
.into()
|
||||
})?;
|
||||
|
||||
Ok((context, surface))
|
||||
}
|
||||
|
||||
fn create_internal_surface(
|
||||
fb_info: skia_safe::gpu::gl::FramebufferInfo,
|
||||
gl_context: &glutin::context::PossiblyCurrentContext,
|
||||
gr_context: &mut skia_safe::gpu::DirectContext,
|
||||
size: PhysicalWindowSize,
|
||||
) -> skia_safe::Surface {
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> Result<skia_safe::Surface, PlatformError> {
|
||||
let config = gl_context.config();
|
||||
|
||||
let backend_render_target = skia_safe::gpu::BackendRenderTarget::new_gl(
|
||||
(size.width.try_into().unwrap(), size.height.try_into().unwrap()),
|
||||
(width, height),
|
||||
Some(config.num_samples() as _),
|
||||
config.stencil_size() as _,
|
||||
fb_info,
|
||||
);
|
||||
let surface = skia_safe::Surface::from_backend_render_target(
|
||||
match skia_safe::Surface::from_backend_render_target(
|
||||
gr_context,
|
||||
&backend_render_target,
|
||||
skia_safe::gpu::SurfaceOrigin::BottomLeft,
|
||||
skia_safe::ColorType::RGBA8888,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
surface
|
||||
) {
|
||||
Some(surface) => Ok(surface),
|
||||
None => {
|
||||
Err("Skia OpenGL Renderer: Failed to allocate internal backend rendering target"
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_context_current(&self) {
|
||||
fn ensure_context_current(&self) -> Result<(), PlatformError> {
|
||||
if !self.glutin_context.is_current() {
|
||||
self.glutin_context.make_current(&self.glutin_surface).unwrap();
|
||||
self.glutin_context.make_current(&self.glutin_surface).map_err(
|
||||
|glutin_error| -> PlatformError {
|
||||
format!("Skia Renderer: Error making context current: {glutin_error}").into()
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OpenGLSurface {
|
||||
fn drop(&mut self) {
|
||||
// Make sure that the context is current before Skia calls glDelete***
|
||||
self.ensure_context_current();
|
||||
self.ensure_context_current().expect("Skia OpenGL Renderer: Failed to make OpenGL context current before deleting graphics resources");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ let instance = Hello::new().unwrap();
|
|||
assert!(!instance.window().is_visible());
|
||||
instance.window().show().unwrap();
|
||||
assert!(instance.window().is_visible());
|
||||
instance.window().hide();
|
||||
instance.window().hide().unwrap();
|
||||
assert!(!instance.window().is_visible());
|
||||
```
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue