mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-29 05:14:48 +00:00

The LineRenderer is going to be a public type which can be used by the MCU board support to draw. Right now, it is used by the old code
456 lines
16 KiB
Rust
456 lines
16 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
use std::cell::{Cell, RefCell};
|
|
use std::rc::{Rc, Weak};
|
|
|
|
use embedded_graphics::prelude::*;
|
|
use embedded_graphics_simulator::SimulatorDisplay;
|
|
use i_slint_core::component::ComponentRc;
|
|
use i_slint_core::graphics::{Image, ImageInner, StaticTextures};
|
|
use i_slint_core::input::KeyboardModifiers;
|
|
use i_slint_core::item_rendering::DirtyRegion;
|
|
use i_slint_core::items::{Item, ItemRef, WindowItem};
|
|
use i_slint_core::layout::Orientation;
|
|
use i_slint_core::window::{PlatformWindow, Window};
|
|
use i_slint_core::{Color, Coord};
|
|
use rgb::FromSlice;
|
|
|
|
use self::event_loop::WinitWindow;
|
|
|
|
type Canvas = femtovg::Canvas<femtovg::renderer::OpenGl>;
|
|
type CanvasRc = Rc<RefCell<Canvas>>;
|
|
|
|
pub mod event_loop;
|
|
mod glcontext;
|
|
use glcontext::*;
|
|
|
|
pub struct SimulatorWindow {
|
|
self_weak: Weak<i_slint_core::window::Window>,
|
|
keyboard_modifiers: std::cell::Cell<KeyboardModifiers>,
|
|
currently_pressed_key_code: std::cell::Cell<Option<winit::event::VirtualKeyCode>>,
|
|
canvas: CanvasRc,
|
|
opengl_context: OpenGLContext,
|
|
constraints: Cell<(i_slint_core::layout::LayoutInfo, i_slint_core::layout::LayoutInfo)>,
|
|
visible: Cell<bool>,
|
|
background_color: Cell<Color>,
|
|
frame_buffer: RefCell<Option<SimulatorDisplay<embedded_graphics::pixelcolor::Rgb888>>>,
|
|
initial_dirty_region_for_next_frame: Cell<DirtyRegion>,
|
|
}
|
|
|
|
impl SimulatorWindow {
|
|
pub(crate) fn new(window_weak: &Weak<i_slint_core::window::Window>) -> Rc<Self> {
|
|
let window_builder = winit::window::WindowBuilder::new().with_visible(false);
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
let (opengl_context, renderer) =
|
|
OpenGLContext::new_context_and_renderer(window_builder, &self.canvas_id);
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
let (opengl_context, renderer) = OpenGLContext::new_context_and_renderer(window_builder);
|
|
|
|
let canvas = femtovg::Canvas::new(renderer).unwrap();
|
|
|
|
opengl_context.make_not_current();
|
|
|
|
let canvas = Rc::new(RefCell::new(canvas));
|
|
|
|
let window_rc = Rc::new(Self {
|
|
self_weak: window_weak.clone(),
|
|
keyboard_modifiers: Default::default(),
|
|
currently_pressed_key_code: Default::default(),
|
|
canvas,
|
|
opengl_context,
|
|
constraints: Default::default(),
|
|
visible: Default::default(),
|
|
background_color: Color::from_rgb_u8(0, 0, 0).into(),
|
|
frame_buffer: RefCell::default(),
|
|
initial_dirty_region_for_next_frame: Default::default(),
|
|
});
|
|
|
|
let runtime_window = window_weak.upgrade().unwrap();
|
|
runtime_window.set_scale_factor(window_rc.opengl_context.window().scale_factor() as _);
|
|
|
|
window_rc
|
|
}
|
|
}
|
|
|
|
impl Drop for SimulatorWindow {
|
|
fn drop(&mut self) {
|
|
crate::event_loop::unregister_window(self.opengl_context.window().id());
|
|
}
|
|
}
|
|
|
|
impl PlatformWindow for SimulatorWindow {
|
|
fn show(self: Rc<Self>) {
|
|
if self.visible.get() {
|
|
return;
|
|
}
|
|
|
|
self.visible.set(true);
|
|
|
|
let runtime_window = self.runtime_window();
|
|
let component_rc = runtime_window.component();
|
|
let component = ComponentRc::borrow_pin(&component_rc);
|
|
let root_item = component.as_ref().get_item_ref(0);
|
|
|
|
let platform_window = self.opengl_context.window();
|
|
|
|
if let Some(window_item) =
|
|
ItemRef::downcast_pin::<i_slint_core::items::WindowItem>(root_item)
|
|
{
|
|
platform_window.set_title(&window_item.title());
|
|
platform_window.set_decorations(!window_item.no_frame());
|
|
};
|
|
|
|
if std::env::var("SLINT_FULLSCREEN").is_ok() {
|
|
platform_window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)))
|
|
} else {
|
|
let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
|
|
let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
|
|
let s = winit::dpi::LogicalSize::new(
|
|
layout_info_h.preferred_bounded(),
|
|
layout_info_v.preferred_bounded(),
|
|
);
|
|
if s.width > 0 as Coord && s.height > 0 as Coord {
|
|
// Make sure that the window's inner size is in sync with the root window item's
|
|
// width/height.
|
|
runtime_window.set_window_item_geometry(s.width, s.height);
|
|
platform_window.set_inner_size(s)
|
|
}
|
|
};
|
|
|
|
platform_window.set_visible(true);
|
|
let id = platform_window.id();
|
|
drop(platform_window);
|
|
crate::event_loop::register_window(id, self);
|
|
}
|
|
|
|
fn hide(self: Rc<Self>) {
|
|
self.opengl_context.window().set_visible(false);
|
|
self.visible.set(false);
|
|
crate::event_loop::unregister_window(self.opengl_context.window().id());
|
|
}
|
|
|
|
fn request_redraw(&self) {
|
|
if self.visible.get() {
|
|
self.opengl_context.window().request_redraw();
|
|
}
|
|
}
|
|
|
|
fn free_graphics_resources<'a>(
|
|
&self,
|
|
items: &mut dyn Iterator<Item = std::pin::Pin<i_slint_core::items::ItemRef<'a>>>,
|
|
) {
|
|
super::LINE_RENDERER.with(|cache| {
|
|
cache.borrow_mut().free_graphics_resources(items);
|
|
});
|
|
}
|
|
|
|
fn show_popup(
|
|
&self,
|
|
popup: &i_slint_core::component::ComponentRc,
|
|
position: i_slint_core::graphics::Point,
|
|
) {
|
|
let runtime_window = self.self_weak.upgrade().unwrap();
|
|
let size = runtime_window.set_active_popup(i_slint_core::window::PopupWindow {
|
|
location: i_slint_core::window::PopupWindowLocation::ChildWindow(position),
|
|
component: popup.clone(),
|
|
});
|
|
|
|
let popup = ComponentRc::borrow_pin(popup);
|
|
let popup_root = popup.as_ref().get_item_ref(0);
|
|
if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
|
|
let width_property =
|
|
i_slint_core::items::WindowItem::FIELD_OFFSETS.width.apply_pin(window_item);
|
|
let height_property =
|
|
i_slint_core::items::WindowItem::FIELD_OFFSETS.height.apply_pin(window_item);
|
|
width_property.set(size.width);
|
|
height_property.set(size.height);
|
|
}
|
|
}
|
|
|
|
fn close_popup(&self, popup: &i_slint_core::window::PopupWindow) {
|
|
match popup.location {
|
|
i_slint_core::window::PopupWindowLocation::TopLevel(_) => {}
|
|
i_slint_core::window::PopupWindowLocation::ChildWindow(offset) => {
|
|
let popup_component = ComponentRc::borrow_pin(&popup.component);
|
|
let popup_root = popup_component.as_ref().get_item_ref(0);
|
|
if let Some(window_item) = ItemRef::downcast_pin::<WindowItem>(popup_root) {
|
|
let popup_region =
|
|
i_slint_core::properties::evaluate_no_tracking(|| window_item.geometry())
|
|
.translate(offset.to_vector());
|
|
|
|
if !popup_region.is_empty() {
|
|
self.initial_dirty_region_for_next_frame.set(
|
|
self.initial_dirty_region_for_next_frame
|
|
.get()
|
|
.union(&popup_region.to_box2d()),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn request_window_properties_update(&self) {
|
|
let window_id = self.opengl_context.window().id();
|
|
crate::event_loop::with_window_target(|event_loop| {
|
|
event_loop
|
|
.event_loop_proxy()
|
|
.send_event(crate::event_loop::CustomEvent::UpdateWindowProperties(window_id))
|
|
})
|
|
.ok();
|
|
}
|
|
|
|
fn apply_window_properties(
|
|
&self,
|
|
window_item: std::pin::Pin<&i_slint_core::items::WindowItem>,
|
|
) {
|
|
WinitWindow::apply_window_properties(self as &dyn WinitWindow, window_item);
|
|
}
|
|
|
|
fn apply_geometry_constraint(
|
|
&self,
|
|
constraints_horizontal: i_slint_core::layout::LayoutInfo,
|
|
constraints_vertical: i_slint_core::layout::LayoutInfo,
|
|
) {
|
|
self.apply_constraints(constraints_horizontal, constraints_vertical)
|
|
}
|
|
|
|
fn set_mouse_cursor(&self, _cursor: i_slint_core::items::MouseCursor) {}
|
|
|
|
fn text_size(
|
|
&self,
|
|
font_request: i_slint_core::graphics::FontRequest,
|
|
text: &str,
|
|
max_width: Option<Coord>,
|
|
) -> i_slint_core::graphics::Size {
|
|
let runtime_window = self.self_weak.upgrade().unwrap();
|
|
crate::fonts::text_size(
|
|
font_request.merge(&self.self_weak.upgrade().unwrap().default_font_properties()),
|
|
text,
|
|
max_width,
|
|
crate::ScaleFactor::new(runtime_window.scale_factor()),
|
|
)
|
|
.to_untyped()
|
|
}
|
|
|
|
fn text_input_byte_offset_for_position(
|
|
&self,
|
|
_text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
|
|
_pos: i_slint_core::graphics::Point,
|
|
) -> usize {
|
|
todo!()
|
|
}
|
|
|
|
fn text_input_cursor_rect_for_byte_offset(
|
|
&self,
|
|
_text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
|
|
_byte_offset: usize,
|
|
) -> i_slint_core::graphics::Rect {
|
|
todo!()
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn core::any::Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl WinitWindow for SimulatorWindow {
|
|
fn runtime_window(&self) -> Rc<i_slint_core::window::Window> {
|
|
self.self_weak.upgrade().unwrap()
|
|
}
|
|
|
|
fn currently_pressed_key_code(&self) -> &Cell<Option<winit::event::VirtualKeyCode>> {
|
|
&self.currently_pressed_key_code
|
|
}
|
|
|
|
fn current_keyboard_modifiers(&self) -> &Cell<KeyboardModifiers> {
|
|
&self.keyboard_modifiers
|
|
}
|
|
|
|
fn draw(self: Rc<Self>) {
|
|
let runtime_window = self.self_weak.upgrade().unwrap();
|
|
|
|
let size = self.opengl_context.window().inner_size();
|
|
|
|
self.opengl_context.with_current_context(|opengl_context| {
|
|
opengl_context.ensure_resized();
|
|
|
|
{
|
|
let mut canvas = self.canvas.borrow_mut();
|
|
// We pass 1.0 as dpi / device pixel ratio as femtovg only uses this factor to scale
|
|
// text metrics. Since we do the entire translation from logical pixels to physical
|
|
// pixels on our end, we don't need femtovg to scale a second time.
|
|
canvas.set_size(size.width, size.height, 1.0);
|
|
}
|
|
|
|
let background =
|
|
crate::renderer::to_rgb888_color_discard_alpha(self.background_color.get());
|
|
|
|
let mut frame_buffer = self.frame_buffer.borrow_mut();
|
|
let display = match frame_buffer.as_mut() {
|
|
Some(buffer)
|
|
if buffer.size().width == size.width && buffer.size().height == size.height =>
|
|
{
|
|
buffer
|
|
}
|
|
_ => {
|
|
let buffer = frame_buffer.insert(SimulatorDisplay::new(Size {
|
|
width: size.width,
|
|
height: size.height,
|
|
}));
|
|
super::LINE_RENDERER.with(|cache| {
|
|
*cache.borrow_mut() = Default::default();
|
|
});
|
|
buffer.clear(background).unwrap();
|
|
buffer
|
|
}
|
|
};
|
|
|
|
struct BufferProvider<'a> {
|
|
devices: &'a mut dyn crate::Devices,
|
|
line_buffer: Vec<crate::TargetPixel>,
|
|
}
|
|
impl crate::renderer::LineBufferProvider for BufferProvider<'_> {
|
|
fn process_line(
|
|
&mut self,
|
|
line: crate::PhysicalLength,
|
|
render_fn: impl FnOnce(&mut [super::TargetPixel]),
|
|
) {
|
|
render_fn(&mut self.line_buffer);
|
|
self.devices.fill_region(
|
|
euclid::rect(0, line.get(), self.line_buffer.len() as _, 1),
|
|
&self.line_buffer,
|
|
);
|
|
}
|
|
}
|
|
super::LINE_RENDERER.with(|renderer| {
|
|
renderer.borrow_mut().render(
|
|
runtime_window,
|
|
self.initial_dirty_region_for_next_frame.take(),
|
|
BufferProvider {
|
|
devices: display,
|
|
line_buffer: vec![Default::default(); size.width as usize],
|
|
},
|
|
)
|
|
});
|
|
|
|
let output_image = display
|
|
.to_rgb_output_image(&embedded_graphics_simulator::OutputSettings::default());
|
|
let image_buffer = output_image.as_image_buffer();
|
|
let image_ref: imgref::ImgRef<rgb::RGB8> = imgref::ImgRef::new(
|
|
image_buffer.as_rgb(),
|
|
image_buffer.width() as usize,
|
|
image_buffer.height() as usize,
|
|
)
|
|
.into();
|
|
|
|
let mut canvas = self.canvas.borrow_mut();
|
|
let image_id = canvas.create_image(image_ref, femtovg::ImageFlags::empty()).unwrap();
|
|
|
|
let mut path = femtovg::Path::new();
|
|
path.rect(0., 0., image_ref.width() as _, image_ref.height() as _);
|
|
|
|
let fill_paint = femtovg::Paint::image(
|
|
image_id,
|
|
0.,
|
|
0.,
|
|
image_ref.width() as _,
|
|
image_ref.height() as _,
|
|
0.0,
|
|
1.0,
|
|
);
|
|
|
|
canvas.fill_path(&mut path, fill_paint);
|
|
|
|
canvas.flush();
|
|
canvas.delete_image(image_id);
|
|
|
|
opengl_context.swap_buffers();
|
|
});
|
|
}
|
|
|
|
fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window)) {
|
|
callback(&*self.opengl_context.window())
|
|
}
|
|
|
|
fn constraints(&self) -> (i_slint_core::layout::LayoutInfo, i_slint_core::layout::LayoutInfo) {
|
|
self.constraints.get()
|
|
}
|
|
fn set_constraints(
|
|
&self,
|
|
constraints: (i_slint_core::layout::LayoutInfo, i_slint_core::layout::LayoutInfo),
|
|
) {
|
|
self.constraints.set(constraints)
|
|
}
|
|
|
|
fn set_background_color(&self, color: Color) {
|
|
self.background_color.set(color);
|
|
}
|
|
fn set_icon(&self, _icon: i_slint_core::graphics::Image) {}
|
|
}
|
|
|
|
pub struct SimulatorBackend;
|
|
|
|
impl i_slint_core::backend::Backend for SimulatorBackend {
|
|
fn create_window(&'static self) -> Rc<Window> {
|
|
i_slint_core::window::Window::new(|window| SimulatorWindow::new(window))
|
|
}
|
|
|
|
fn run_event_loop(&'static self, behavior: i_slint_core::backend::EventLoopQuitBehavior) {
|
|
event_loop::run(behavior);
|
|
std::process::exit(0);
|
|
}
|
|
|
|
fn quit_event_loop(&'static self) {
|
|
self::event_loop::with_window_target(|event_loop| {
|
|
event_loop.event_loop_proxy().send_event(self::event_loop::CustomEvent::Exit).ok();
|
|
})
|
|
}
|
|
|
|
fn register_font_from_memory(
|
|
&'static self,
|
|
_data: &'static [u8],
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
//TODO
|
|
Err("Not implemented".into())
|
|
}
|
|
|
|
fn register_font_from_path(
|
|
&'static self,
|
|
_path: &std::path::Path,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn register_bitmap_font(&'static self, font_data: &'static i_slint_core::graphics::BitmapFont) {
|
|
crate::fonts::register_bitmap_font(font_data);
|
|
}
|
|
|
|
fn set_clipboard_text(&'static self, _text: String) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn clipboard_text(&'static self) -> Option<String> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn post_event(&'static self, event: Box<dyn FnOnce() + Send>) {
|
|
self::event_loop::GLOBAL_PROXY
|
|
.get_or_init(Default::default)
|
|
.lock()
|
|
.unwrap()
|
|
.send_event(self::event_loop::CustomEvent::UserEvent(event));
|
|
}
|
|
|
|
fn image_size(&'static self, image: &Image) -> i_slint_core::graphics::IntSize {
|
|
let inner: &ImageInner = image.into();
|
|
match inner {
|
|
ImageInner::None => Default::default(),
|
|
ImageInner::AbsoluteFilePath(_) | ImageInner::EmbeddedData { .. } => unimplemented!(),
|
|
ImageInner::EmbeddedImage(buffer) => buffer.size(),
|
|
ImageInner::StaticTextures(StaticTextures { original_size, .. }) => *original_size,
|
|
}
|
|
}
|
|
}
|