Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for invalidating a single rectangle. #817

Merged
merged 7 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions druid-shell/examples/invalidate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2020 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::any::Any;

use std::time::Instant;

use druid_shell::kurbo::{Point, Rect};
use druid_shell::piet::{Color, Piet, RenderContext};

use druid_shell::{Application, WinHandler, WindowBuilder, WindowHandle};

struct InvalidateTest {
handle: WindowHandle,
size: (f64, f64),
start_time: Instant,
color: Color,
rect: Rect,
}

impl InvalidateTest {
fn update_color_and_rect(&mut self) {
let time_since_start = (Instant::now() - self.start_time).as_nanos();
let (r, g, b, _) = self.color.as_rgba_u8();
self.color = match (time_since_start % 2, time_since_start % 3) {
(0, _) => Color::rgb8(r.wrapping_add(10), g, b),
(_, 0) => Color::rgb8(r, g.wrapping_add(10), b),
(_, _) => Color::rgb8(r, g, b.wrapping_add(10)),
};

self.rect.x0 = (self.rect.x0 + 5.0) % self.size.0;
self.rect.x1 = (self.rect.x1 + 5.5) % self.size.0;
self.rect.y0 = (self.rect.y0 + 3.0) % self.size.1;
self.rect.y1 = (self.rect.y1 + 3.5) % self.size.1;
}
}

impl WinHandler for InvalidateTest {
fn connect(&mut self, handle: &WindowHandle) {
self.handle = handle.clone();
}

fn paint(&mut self, piet: &mut Piet, rect: Rect) -> bool {
self.update_color_and_rect();
piet.fill(rect, &self.color);

self.handle.invalidate_rect(self.rect);
false
}
Copy link
Member

@cmyr cmyr Apr 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on mac, invalidation calls that happen during drawing are ignored.

I would suggest changing this to use a timer, something like:

    fn connect(&mut self, handle: &WindowHandle) {
        self.handle = handle.clone();
        self.handle.request_timer(std::time::Duration::from_millis(60));
    }
    
    fn timer(&mut self, _id: TimerToken) {
        self.update_color_and_rect();
        self.handle.invalidate_rect(self.rect);
        self.handle.request_timer(std::time::Duration::from_millis(60));
    }

    fn paint(&mut self, piet: &mut Piet, rect: Rect) -> bool {
        piet.fill(rect, &self.color);
        false
    }


fn size(&mut self, width: u32, height: u32) {
let dpi = self.handle.get_dpi();
let dpi_scale = dpi as f64 / 96.0;
let width_f = (width as f64) / dpi_scale;
let height_f = (height as f64) / dpi_scale;
self.size = (width_f, height_f);
}

fn command(&mut self, id: u32) {
match id {
0x100 => self.handle.close(),
_ => println!("unexpected id {}", id),
}
}

fn as_any(&mut self) -> &mut dyn Any {
self
}
}

fn main() {
let mut app = Application::new(None);
let mut builder = WindowBuilder::new();
let inv_test = InvalidateTest {
size: Default::default(),
handle: Default::default(),
start_time: Instant::now(),
rect: Rect::from_origin_size(Point::ZERO, (10.0, 20.0)),
color: Color::WHITE,
};
builder.set_handler(Box::new(inv_test));
builder.set_title("Invalidate tester");

let window = builder.build().unwrap();
window.show();
app.run();
}
2 changes: 1 addition & 1 deletion druid-shell/examples/perftest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl WinHandler for PerfTest {
self.handle = handle.clone();
}

fn paint(&mut self, piet: &mut Piet) -> bool {
fn paint(&mut self, piet: &mut Piet, _: Rect) -> bool {
let (width, height) = self.size;
let rect = Rect::new(0.0, 0.0, width, height);
piet.fill(rect, &BG_COLOR);
Expand Down
2 changes: 1 addition & 1 deletion druid-shell/examples/shello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl WinHandler for HelloState {
self.handle = handle.clone();
}

fn paint(&mut self, piet: &mut piet_common::Piet) -> bool {
fn paint(&mut self, piet: &mut piet_common::Piet, _: Rect) -> bool {
let (width, height) = self.size;
let rect = Rect::new(0.0, 0.0, width, height);
piet.fill(rect, &BG_COLOR);
Expand Down
33 changes: 26 additions & 7 deletions druid-shell/src/platform/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use gio::ApplicationExt;
use gtk::prelude::*;
use gtk::{AccelGroup, ApplicationWindow};

use crate::kurbo::{Point, Size, Vec2};
use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::piet::{Piet, RenderContext};

use super::application::with_application;
Expand Down Expand Up @@ -242,14 +242,11 @@ impl WindowBuilder {
drawing_area.connect_draw(clone!(handle => move |widget, context| {
if let Some(state) = handle.state.upgrade() {

let extents = context.clip_extents();
let extents = widget.get_allocation();
let dpi_scale = state.window.get_window()
.map(|w| w.get_display().get_default_screen().get_resolution())
.unwrap_or(96.0) / 96.0;
let size = (
((extents.2 - extents.0) * dpi_scale) as u32,
((extents.3 - extents.1) * dpi_scale) as u32,
);
let size = ((extents.width as f64 * dpi_scale) as u32, (extents.height as f64 * dpi_scale) as u32);

if last_size.get() != size {
last_size.set(size);
Expand All @@ -258,11 +255,13 @@ impl WindowBuilder {

// For some reason piet needs a mutable context, so give it one I guess.
let mut context = context.clone();
let (x0, y0, x1, y1) = context.clip_extents();
let mut piet_context = Piet::new(&mut context);

if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() {
let invalid_rect = Rect::new(x0 * dpi_scale, y0 * dpi_scale, x1 * dpi_scale, y1 * dpi_scale);
let anim = handler_borrow
.paint(&mut piet_context);
.paint(&mut piet_context, invalid_rect);
if let Err(e) = piet_context.finish() {
eprintln!("piet error on render: {:?}", e);
}
Expand Down Expand Up @@ -508,6 +507,26 @@ impl WindowHandle {
}
}

/// Request invalidation of one rectangle.
pub fn invalidate_rect(&self, rect: Rect) {
let dpi_scale = self.get_dpi() as f64 / 96.0;
let rect = Rect::from_origin_size(
(rect.x0 * dpi_scale, rect.y0 * dpi_scale),
rect.size() * dpi_scale,
);

// GTK+ takes rects with integer coordinates, and non-negative width/height.
let r = rect.abs().expand();
if let Some(state) = self.state.upgrade() {
state.window.queue_draw_area(
r.x0 as i32,
r.y0 as i32,
r.width() as i32,
r.height() as i32,
);
}
}

pub fn text(&self) -> Text {
Text::new()
}
Expand Down
20 changes: 18 additions & 2 deletions druid-shell/src/platform/mac/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use objc::{class, msg_send, sel, sel_impl};
use cairo::{Context, QuartzSurface};
use log::{error, info};

use crate::kurbo::{Point, Size, Vec2};
use crate::kurbo::{Point, Rect, Size, Vec2};
use crate::piet::{Piet, RenderContext};

use super::dialog;
Expand Down Expand Up @@ -503,6 +503,10 @@ extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) {
let frame = NSView::frame(this as *mut _);
let width = frame.size.width as u32;
let height = frame.size.height as u32;
let rect = Rect::from_origin_size(
(dirtyRect.origin.x, dirtyRect.origin.y),
(dirtyRect.size.width, dirtyRect.size.height),
);
let cairo_surface =
QuartzSurface::create_for_cg_context(cgcontext, width, height).expect("cairo surface");
let mut cairo_ctx = Context::new(&cairo_surface);
Expand All @@ -511,7 +515,7 @@ extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) {
let mut piet_ctx = Piet::new(&mut cairo_ctx);
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let anim = (*view_state).handler.paint(&mut piet_ctx);
let anim = (*view_state).handler.paint(&mut piet_ctx, rect);
if let Err(e) = piet_ctx.finish() {
error!("{}", e)
}
Expand Down Expand Up @@ -639,6 +643,18 @@ impl WindowHandle {
}
}

/// Request invalidation of one rectangle.
pub fn invalidate_rect(&self, rect: Rect) {
let rect = NSRect::new(
NSPoint::new(rect.x0, rect.y0),
NSSize::new(rect.width(), rect.height()),
);
unsafe {
// We could share impl with redraw, but we'd need to deal with nil.
let () = msg_send![*self.nsview.load(), setNeedsDisplay: rect];
jneem marked this conversation as resolved.
Show resolved Hide resolved
cmyr marked this conversation as resolved.
Show resolved Hide resolved
}
}

pub fn set_cursor(&mut self, cursor: &Cursor) {
unsafe {
let nscursor = class!(NSCursor);
Expand Down
31 changes: 27 additions & 4 deletions druid-shell/src/platform/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use instant::Instant;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

use crate::kurbo::{Point, Size, Vec2};
use crate::kurbo::{Point, Rect, Size, Vec2};

use crate::piet::RenderContext;

Expand Down Expand Up @@ -89,14 +89,15 @@ struct WindowState {
window: web_sys::Window,
canvas: web_sys::HtmlCanvasElement,
context: web_sys::CanvasRenderingContext2d,
invalid_rect: Cell<Rect>,
}

impl WindowState {
fn render(&self) -> bool {
fn render(&self, rect: Rect) -> bool {
self.context
.clear_rect(0.0, 0.0, self.get_width() as f64, self.get_height() as f64);
let mut piet_ctx = piet_common::Piet::new(self.context.clone(), self.window.clone());
let want_anim_frame = self.handler.borrow_mut().paint(&mut piet_ctx);
let want_anim_frame = self.handler.borrow_mut().paint(&mut piet_ctx, rect);
if let Err(e) = piet_ctx.finish() {
log::error!("piet error on render: {:?}", e);
}
Expand Down Expand Up @@ -370,6 +371,7 @@ impl WindowBuilder {
window,
canvas,
context,
invalid_rect: Cell::new(Rect::ZERO),
});

setup_web_callbacks(&window);
Expand Down Expand Up @@ -411,7 +413,27 @@ impl WindowHandle {
log::warn!("bring_to_frontand_focus unimplemented for web");
}

pub fn invalidate_rect(&self, rect: Rect) {
if let Some(s) = self.0.upgrade() {
let cur_rect = s.invalid_rect.get();
if cur_rect.width() == 0.0 || cur_rect.height() == 0.0 {
s.invalid_rect.set(rect);
} else if rect.width() != 0.0 && rect.height() != 0.0 {
s.invalid_rect.set(cur_rect.union(rect));
}
}
self.render_soon();
}

pub fn invalidate(&self) {
if let Some(s) = self.0.upgrade() {
let rect = Rect::from_origin_size(
Point::ORIGIN,
// FIXME: does this need scaling? Not sure exactly where dpr enters...
(s.get_width() as f64, s.get_height() as f64),
);
s.invalid_rect.set(rect);
}
self.render_soon();
}

Expand Down Expand Up @@ -478,9 +500,10 @@ impl WindowHandle {
fn render_soon(&self) {
if let Some(s) = self.0.upgrade() {
let handle = self.clone();
let rect = s.invalid_rect.get();
let state = s.clone();
s.request_animation_frame(move || {
let want_anim_frame = state.render();
let want_anim_frame = state.render(rect);
if want_anim_frame {
handle.render_soon();
}
Expand Down
Loading