mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-05 18:10:42 +00:00
Partial renderer: Initial work
This commit is contained in:
parent
b6ef333444
commit
b2caa757e7
4 changed files with 269 additions and 24 deletions
|
@ -77,6 +77,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! { static DEVICES: RefCell<Option<Box<dyn Devices + 'static>>> = RefCell::new(None) }
|
thread_local! { static DEVICES: RefCell<Option<Box<dyn Devices + 'static>>> = RefCell::new(None) }
|
||||||
|
thread_local! { static PARTIAL_RENDERING_CACHE: RefCell<i_slint_core::item_rendering::PartialRenderingCache> = RefCell::new(Default::default()) }
|
||||||
|
|
||||||
mod the_backend {
|
mod the_backend {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -223,7 +224,14 @@ mod the_backend {
|
||||||
runtime_window.set_window_item_geometry(size.width as _, size.height as _);
|
runtime_window.set_window_item_geometry(size.width as _, size.height as _);
|
||||||
let background =
|
let background =
|
||||||
crate::renderer::to_rgb888_color_discard_alpha(window.background_color.get());
|
crate::renderer::to_rgb888_color_discard_alpha(window.background_color.get());
|
||||||
crate::renderer::render_window_frame(runtime_window, background, &mut **devices);
|
PARTIAL_RENDERING_CACHE.with(|cache| {
|
||||||
|
crate::renderer::render_window_frame(
|
||||||
|
runtime_window,
|
||||||
|
background,
|
||||||
|
&mut **devices,
|
||||||
|
&mut cache.borrow_mut(),
|
||||||
|
)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ use alloc::collections::VecDeque;
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
use alloc::{vec, vec::Vec};
|
use alloc::{vec, vec::Vec};
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
|
|
||||||
use embedded_graphics::pixelcolor::Rgb888;
|
use embedded_graphics::pixelcolor::Rgb888;
|
||||||
use embedded_graphics::prelude::*;
|
use embedded_graphics::prelude::*;
|
||||||
use i_slint_core::graphics::{FontRequest, IntRect, PixelFormat, Rect as RectF};
|
use i_slint_core::graphics::{FontRequest, IntRect, PixelFormat, Rect as RectF};
|
||||||
|
use i_slint_core::item_rendering::PartialRenderingCache;
|
||||||
use i_slint_core::{Color, ImageInner, StaticTextures};
|
use i_slint_core::{Color, ImageInner, StaticTextures};
|
||||||
|
|
||||||
use crate::fonts::FontMetrics;
|
use crate::fonts::FontMetrics;
|
||||||
|
@ -19,13 +19,16 @@ use crate::{
|
||||||
|
|
||||||
use euclid::num::Zero;
|
use euclid::num::Zero;
|
||||||
|
|
||||||
|
type DirtyRegion = PhysicalRect;
|
||||||
|
|
||||||
pub fn render_window_frame(
|
pub fn render_window_frame(
|
||||||
runtime_window: Rc<i_slint_core::window::Window>,
|
runtime_window: Rc<i_slint_core::window::Window>,
|
||||||
background: Rgb888,
|
background: Rgb888,
|
||||||
devices: &mut dyn Devices,
|
devices: &mut dyn Devices,
|
||||||
|
cache: &mut PartialRenderingCache,
|
||||||
) {
|
) {
|
||||||
let size = devices.screen_size();
|
let size = devices.screen_size();
|
||||||
let mut scene = prepare_scene(runtime_window, size, devices);
|
let mut scene = prepare_scene(runtime_window, size, devices, cache);
|
||||||
|
|
||||||
/*for item in scene.future_items {
|
/*for item in scene.future_items {
|
||||||
match item.command {
|
match item.command {
|
||||||
|
@ -72,11 +75,21 @@ pub fn render_window_frame(
|
||||||
let mut screen_fill_profiler = profiler::Timer::new_stopped();
|
let mut screen_fill_profiler = profiler::Timer::new_stopped();
|
||||||
|
|
||||||
let mut line_buffer = vec![background; size.width as usize];
|
let mut line_buffer = vec![background; size.width as usize];
|
||||||
while scene.current_line < size.height_length() {
|
|
||||||
|
let dirty_region = scene
|
||||||
|
.dirty_region
|
||||||
|
.intersection(&PhysicalRect { origin: euclid::point2(0, 0), size })
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
while scene.current_line < dirty_region.origin.y_length() + dirty_region.size.height_length() {
|
||||||
line_buffer.fill(background);
|
line_buffer.fill(background);
|
||||||
line_processing_profiler.start(devices);
|
line_processing_profiler.start(devices);
|
||||||
let line = scene.process_line();
|
let line = scene.process_line();
|
||||||
line_processing_profiler.stop(devices);
|
line_processing_profiler.stop(devices);
|
||||||
|
if scene.current_line < dirty_region.origin.y_length() {
|
||||||
|
// FIXME: ideally we should start with that coordinate and not call process_line for all the lines before
|
||||||
|
continue;
|
||||||
|
}
|
||||||
span_drawing_profiler.start(devices);
|
span_drawing_profiler.start(devices);
|
||||||
for span in line.spans.iter().rev() {
|
for span in line.spans.iter().rev() {
|
||||||
match span.command {
|
match span.command {
|
||||||
|
@ -159,7 +172,11 @@ pub fn render_window_frame(
|
||||||
}
|
}
|
||||||
span_drawing_profiler.stop(devices);
|
span_drawing_profiler.stop(devices);
|
||||||
screen_fill_profiler.start(devices);
|
screen_fill_profiler.start(devices);
|
||||||
devices.fill_region(euclid::rect(0, line.line.get() as i16, size.width, 1), &line_buffer);
|
devices.fill_region(
|
||||||
|
euclid::rect(dirty_region.origin.x, line.line.get() as i16, dirty_region.size.width, 1),
|
||||||
|
&line_buffer[dirty_region.origin.x as usize
|
||||||
|
..(dirty_region.origin.x + dirty_region.size.width) as usize],
|
||||||
|
);
|
||||||
screen_fill_profiler.stop(devices);
|
screen_fill_profiler.stop(devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,10 +202,17 @@ struct Scene {
|
||||||
|
|
||||||
rectangles: Vec<Color>,
|
rectangles: Vec<Color>,
|
||||||
textures: Vec<SceneTexture>,
|
textures: Vec<SceneTexture>,
|
||||||
|
|
||||||
|
dirty_region: DirtyRegion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
fn new(mut items: Vec<SceneItem>, rectangles: Vec<Color>, textures: Vec<SceneTexture>) -> Self {
|
fn new(
|
||||||
|
mut items: Vec<SceneItem>,
|
||||||
|
rectangles: Vec<Color>,
|
||||||
|
textures: Vec<SceneTexture>,
|
||||||
|
dirty_region: DirtyRegion,
|
||||||
|
) -> Self {
|
||||||
items.sort_unstable_by(|a, b| compare_scene_item(a, b).reverse());
|
items.sort_unstable_by(|a, b| compare_scene_item(a, b).reverse());
|
||||||
Self {
|
Self {
|
||||||
future_items: items,
|
future_items: items,
|
||||||
|
@ -197,6 +221,7 @@ impl Scene {
|
||||||
next_items: Default::default(),
|
next_items: Default::default(),
|
||||||
rectangles,
|
rectangles,
|
||||||
textures,
|
textures,
|
||||||
|
dirty_region,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,24 +315,30 @@ fn prepare_scene(
|
||||||
runtime_window: Rc<i_slint_core::window::Window>,
|
runtime_window: Rc<i_slint_core::window::Window>,
|
||||||
size: PhysicalSize,
|
size: PhysicalSize,
|
||||||
devices: &dyn Devices,
|
devices: &dyn Devices,
|
||||||
|
cache: &mut PartialRenderingCache,
|
||||||
) -> Scene {
|
) -> Scene {
|
||||||
let prepare_scene_profiler = profiler::Timer::new(devices);
|
let prepare_scene_profiler = profiler::Timer::new(devices);
|
||||||
let mut prepare_scene = PrepareScene::new(
|
let factor = ScaleFactor::new(runtime_window.scale_factor());
|
||||||
size,
|
let prepare_scene = PrepareScene::new(size, factor, runtime_window.default_font_properties());
|
||||||
ScaleFactor::new(runtime_window.scale_factor()),
|
let mut renderer = i_slint_core::item_rendering::PartialRenderer::new(cache, prepare_scene);
|
||||||
runtime_window.default_font_properties(),
|
|
||||||
);
|
let mut dirty_region = i_slint_core::item_rendering::DirtyRegion::default();
|
||||||
|
|
||||||
runtime_window.draw_contents(|components| {
|
runtime_window.draw_contents(|components| {
|
||||||
for (component, origin) in components {
|
for (component, origin) in components {
|
||||||
i_slint_core::item_rendering::render_component_items(
|
renderer.compute_dirty_regions(component, *origin);
|
||||||
component,
|
dirty_region = dirty_region.union(&renderer.dirty_region);
|
||||||
&mut prepare_scene,
|
i_slint_core::item_rendering::render_component_items(component, &mut renderer, *origin);
|
||||||
origin.clone(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
prepare_scene_profiler.stop_profiling(devices, "prepare_scene");
|
prepare_scene_profiler.stop_profiling(devices, "prepare_scene");
|
||||||
Scene::new(prepare_scene.items, prepare_scene.rectangles, prepare_scene.textures)
|
let prepare_scene = renderer.into_inner();
|
||||||
|
Scene::new(
|
||||||
|
prepare_scene.items,
|
||||||
|
prepare_scene.rectangles,
|
||||||
|
prepare_scene.textures,
|
||||||
|
(euclid::Rect::from_untyped(&dirty_region.to_rect()) * factor).round_out().cast(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PrepareScene {
|
struct PrepareScene {
|
||||||
|
|
|
@ -267,7 +267,14 @@ impl WinitWindow for SimulatorWindow {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::renderer::render_window_frame(runtime_window, background, &mut display);
|
super::PARTIAL_RENDERING_CACHE.with(|cache| {
|
||||||
|
crate::renderer::render_window_frame(
|
||||||
|
runtime_window,
|
||||||
|
background,
|
||||||
|
&mut display,
|
||||||
|
&mut cache.borrow_mut(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
let output_image = display
|
let output_image = display
|
||||||
.to_rgb_output_image(&embedded_graphics_simulator::OutputSettings::default());
|
.to_rgb_output_image(&embedded_graphics_simulator::OutputSettings::default());
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
use super::graphics::RenderingCache;
|
use super::graphics::RenderingCache;
|
||||||
use super::items::*;
|
use super::items::*;
|
||||||
use crate::component::ComponentRc;
|
use crate::component::ComponentRc;
|
||||||
use crate::graphics::Rect;
|
use crate::graphics::{CachedGraphicsData, Point, Rect};
|
||||||
use crate::item_tree::ItemVisitorResult;
|
use crate::item_tree::ItemVisitorResult;
|
||||||
|
use alloc::boxed::Box;
|
||||||
use core::cell::{Cell, RefCell};
|
use core::cell::{Cell, RefCell};
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
|
|
||||||
|
@ -34,11 +35,9 @@ impl CachedRenderingData {
|
||||||
cache: &RefCell<RenderingCache<T>>,
|
cache: &RefCell<RenderingCache<T>>,
|
||||||
update_fn: impl FnOnce() -> T,
|
update_fn: impl FnOnce() -> T,
|
||||||
) -> T {
|
) -> T {
|
||||||
if self.cache_generation.get() == cache.borrow().generation()
|
if let Some(entry) = self.get_entry(&mut cache.borrow_mut()) {
|
||||||
&& cache.borrow().contains(self.cache_index.get())
|
|
||||||
{
|
|
||||||
let index = self.cache_index.get();
|
let index = self.cache_index.get();
|
||||||
let tracker = cache.borrow_mut().get_mut(index).unwrap().dependency_tracker.take();
|
let tracker = entry.dependency_tracker.take();
|
||||||
|
|
||||||
let maybe_new_data =
|
let maybe_new_data =
|
||||||
tracker.as_ref().and_then(|tracker| tracker.as_ref().evaluate_if_dirty(update_fn));
|
tracker.as_ref().and_then(|tracker| tracker.as_ref().evaluate_if_dirty(update_fn));
|
||||||
|
@ -71,6 +70,19 @@ impl CachedRenderingData {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the value if it is in the cache
|
||||||
|
pub fn get_entry<'a, T>(
|
||||||
|
&self,
|
||||||
|
cache: &'a mut RenderingCache<T>,
|
||||||
|
) -> Option<&'a mut crate::graphics::CachedGraphicsData<T>> {
|
||||||
|
let index = self.cache_index.get();
|
||||||
|
if self.cache_generation.get() == cache.generation() {
|
||||||
|
cache.get_mut(index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the item might be a clipping item
|
/// Return true if the item might be a clipping item
|
||||||
|
@ -85,7 +97,7 @@ pub(crate) fn is_clipping_item(item: Pin<ItemRef>) -> bool {
|
||||||
pub fn render_component_items(
|
pub fn render_component_items(
|
||||||
component: &ComponentRc,
|
component: &ComponentRc,
|
||||||
renderer: &mut dyn ItemRenderer,
|
renderer: &mut dyn ItemRenderer,
|
||||||
origin: crate::graphics::Point,
|
origin: Point,
|
||||||
) {
|
) {
|
||||||
renderer.save_state();
|
renderer.save_state();
|
||||||
renderer.translate(origin.x, origin.y);
|
renderer.translate(origin.x, origin.y);
|
||||||
|
@ -188,3 +200,190 @@ pub trait ItemRenderer {
|
||||||
/// Return the internal renderer
|
/// Return the internal renderer
|
||||||
fn as_any(&mut self) -> &mut dyn core::any::Any;
|
fn as_any(&mut self) -> &mut dyn core::any::Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The cache that needs to be held by the Window for the partial rendering
|
||||||
|
pub type PartialRenderingCache = RenderingCache<Rect>;
|
||||||
|
|
||||||
|
/// FIXME: Should actually be a region and not just a rectangle
|
||||||
|
pub type DirtyRegion = euclid::default::Box2D<f32>;
|
||||||
|
|
||||||
|
/// Put this structure in the renderer to help with partial rendering
|
||||||
|
pub struct PartialRenderer<'a, T> {
|
||||||
|
cache: &'a mut PartialRenderingCache,
|
||||||
|
/// The region of the screen which is considered dirty and that should be repainted
|
||||||
|
pub dirty_region: DirtyRegion,
|
||||||
|
actual_renderer: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> PartialRenderer<'a, T> {
|
||||||
|
/// Create a new PartialRenderer
|
||||||
|
pub fn new(cache: &'a mut PartialRenderingCache, actual_renderer: T) -> Self {
|
||||||
|
Self { cache, dirty_region: Default::default(), actual_renderer }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit the tree of item and compute what are the dirty regions
|
||||||
|
pub fn compute_dirty_regions(&mut self, component: &ComponentRc, origin: Point) {
|
||||||
|
crate::item_tree::visit_items(
|
||||||
|
component,
|
||||||
|
crate::item_tree::TraversalOrder::BackToFront,
|
||||||
|
|_, item, _, offset| match item
|
||||||
|
.cached_rendering_data_offset()
|
||||||
|
.get_entry(&mut self.cache)
|
||||||
|
{
|
||||||
|
Some(CachedGraphicsData { data, dependency_tracker: Some(tr) }) => {
|
||||||
|
if tr.is_dirty() {
|
||||||
|
let geom = item.as_ref().geometry();
|
||||||
|
let old_geom = *data;
|
||||||
|
self.mark_dirty_rect(old_geom, *offset);
|
||||||
|
self.mark_dirty_rect(geom, *offset);
|
||||||
|
ItemVisitorResult::Continue(*offset + geom.origin.to_vector())
|
||||||
|
} else {
|
||||||
|
ItemVisitorResult::Continue(*offset + data.origin.to_vector())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let geom = item.as_ref().geometry();
|
||||||
|
self.mark_dirty_rect(geom, *offset);
|
||||||
|
ItemVisitorResult::Continue(*offset + geom.origin.to_vector())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
origin.to_vector(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_dirty_rect(&mut self, rect: Rect, offset: euclid::default::Vector2D<f32>) {
|
||||||
|
if !rect.is_empty() {
|
||||||
|
self.dirty_region = self.dirty_region.union(&rect.translate(offset).to_box2d());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_rendering(
|
||||||
|
cache: &mut PartialRenderingCache,
|
||||||
|
rendering_data: &CachedRenderingData,
|
||||||
|
render_fn: impl FnOnce() -> Rect,
|
||||||
|
) {
|
||||||
|
if let Some(entry) = rendering_data.get_entry(cache) {
|
||||||
|
entry
|
||||||
|
.dependency_tracker
|
||||||
|
.get_or_insert_with(|| Box::pin(crate::properties::PropertyTracker::default()))
|
||||||
|
.as_ref()
|
||||||
|
.evaluate(render_fn);
|
||||||
|
} else {
|
||||||
|
let cache_entry = crate::graphics::CachedGraphicsData::new(render_fn);
|
||||||
|
rendering_data.cache_index.set(cache.insert(cache_entry));
|
||||||
|
rendering_data.cache_generation.set(cache.generation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the actual renderer
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.actual_renderer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! forward_rendering_call {
|
||||||
|
(fn $fn:ident($Ty:ty)) => {
|
||||||
|
fn $fn(&mut self, obj: Pin<&$Ty>) {
|
||||||
|
Self::do_rendering(&mut self.cache, &obj.cached_rendering_data, || {
|
||||||
|
self.actual_renderer.$fn(obj);
|
||||||
|
type Ty = $Ty;
|
||||||
|
let width = Ty::FIELD_OFFSETS.width.apply_pin(obj).get_untracked();
|
||||||
|
let height = Ty::FIELD_OFFSETS.height.apply_pin(obj).get_untracked();
|
||||||
|
let x = Ty::FIELD_OFFSETS.x.apply_pin(obj).get_untracked();
|
||||||
|
let y = Ty::FIELD_OFFSETS.y.apply_pin(obj).get_untracked();
|
||||||
|
euclid::rect(x, y, width, height)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ItemRenderer> ItemRenderer for PartialRenderer<'a, T> {
|
||||||
|
fn filter_item(&mut self, item: Pin<ItemRef>) -> (bool, Rect) {
|
||||||
|
let rendering_data = item.cached_rendering_data_offset();
|
||||||
|
let item_geometry = match rendering_data.get_entry(&mut self.cache) {
|
||||||
|
Some(CachedGraphicsData { data, dependency_tracker }) => {
|
||||||
|
dependency_tracker
|
||||||
|
.get_or_insert_with(|| Box::pin(crate::properties::PropertyTracker::default()))
|
||||||
|
.as_ref()
|
||||||
|
.evaluate_if_dirty(|| *data = item.as_ref().geometry());
|
||||||
|
*data
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let cache_entry =
|
||||||
|
crate::graphics::CachedGraphicsData::new(|| item.as_ref().geometry());
|
||||||
|
let geom = cache_entry.data;
|
||||||
|
rendering_data.cache_index.set(self.cache.insert(cache_entry));
|
||||||
|
rendering_data.cache_generation.set(self.cache.generation());
|
||||||
|
geom
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//let clip = self.get_current_clip().intersection(&self.dirty_region.to_rect());
|
||||||
|
//let draw = clip.map_or(false, |r| r.intersects(&item_geometry));
|
||||||
|
//FIXME: the dirty_region is in global coordinate but item_geometry and current_clip is not
|
||||||
|
let draw = self.get_current_clip().intersects(&item_geometry);
|
||||||
|
(draw, item_geometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_rendering_call!(fn draw_rectangle(Rectangle));
|
||||||
|
forward_rendering_call!(fn draw_border_rectangle(BorderRectangle));
|
||||||
|
forward_rendering_call!(fn draw_image(ImageItem));
|
||||||
|
forward_rendering_call!(fn draw_clipped_image(ClippedImage));
|
||||||
|
forward_rendering_call!(fn draw_text(Text));
|
||||||
|
forward_rendering_call!(fn draw_text_input(TextInput));
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
forward_rendering_call!(fn draw_path(Path));
|
||||||
|
forward_rendering_call!(fn draw_box_shadow(BoxShadow));
|
||||||
|
|
||||||
|
fn combine_clip(&mut self, rect: Rect, radius: f32, border_width: f32) {
|
||||||
|
self.actual_renderer.combine_clip(rect, radius, border_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_clip(&self) -> Rect {
|
||||||
|
self.actual_renderer.get_current_clip()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate(&mut self, x: f32, y: f32) {
|
||||||
|
self.actual_renderer.translate(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(&mut self, angle_in_degrees: f32) {
|
||||||
|
self.actual_renderer.rotate(angle_in_degrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_opacity(&mut self, opacity: f32) {
|
||||||
|
self.actual_renderer.apply_opacity(opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_state(&mut self) {
|
||||||
|
self.actual_renderer.save_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_state(&mut self) {
|
||||||
|
self.actual_renderer.restore_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self) -> f32 {
|
||||||
|
self.actual_renderer.scale_factor()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_cached_pixmap(
|
||||||
|
&mut self,
|
||||||
|
item_cache: &CachedRenderingData,
|
||||||
|
update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
|
||||||
|
) {
|
||||||
|
self.actual_renderer.draw_cached_pixmap(item_cache, update_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_string(&mut self, string: &str, color: crate::Color) {
|
||||||
|
self.actual_renderer.draw_string(string, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> crate::window::WindowRc {
|
||||||
|
self.actual_renderer.window()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&mut self) -> &mut dyn core::any::Any {
|
||||||
|
self.actual_renderer.as_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue