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

[parley] new selection logic and an editor example #106

Merged
merged 18 commits into from
Aug 22, 2024
Merged
3,201 changes: 2,941 additions & 260 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"parley",
"examples/tiny_skia_render",
"examples/swash_render",
"examples/vello_editor",
]

[workspace.package]
Expand Down
1 change: 0 additions & 1 deletion examples/swash_render/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ fn main() {
let mut layout: Layout<Color> = builder.build();

// Perform layout (including bidi resolution and shaping) with start alignment
layout.break_all_lines(max_advance);
layout.align(max_advance, Alignment::Start);

// Create image to render into
Expand Down
19 changes: 19 additions & 0 deletions examples/vello_editor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "vello_editor"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
vello = "0.2.1"
anyhow = "1.0.86"
pollster = "0.3.0"
winit = "0.30.3"
parley = { workspace = true, default-features = true }
peniko = { workspace = true }
clipboard-rs = "0.1.11"

[lints]
workspace = true
223 changes: 223 additions & 0 deletions examples/vello_editor/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2024 the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use anyhow::Result;
use std::num::NonZeroUsize;
use std::sync::Arc;
use vello::peniko::Color;
use vello::util::{RenderContext, RenderSurface};
use vello::wgpu;
use vello::{AaConfig, Renderer, RendererOptions, Scene};
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::event::*;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::Window;

// #[path = "text2.rs"]
mod text;

// Simple struct to hold the state of the renderer
pub struct ActiveRenderState<'s> {
// The fields MUST be in this order, so that the surface is dropped before the window
surface: RenderSurface<'s>,
window: Arc<Window>,
}

enum RenderState<'s> {
Active(ActiveRenderState<'s>),
// Cache a window so that it can be reused when the app is resumed after being suspended
Suspended(Option<Arc<Window>>),
}

struct SimpleVelloApp<'s> {
// The vello RenderContext which is a global context that lasts for the
// lifetime of the application
context: RenderContext,

// An array of renderers, one per wgpu device
renderers: Vec<Option<Renderer>>,

// State for our example where we store the winit Window and the wgpu Surface
state: RenderState<'s>,

// A vello Scene which is a data structure which allows one to build up a
// description a scene to be drawn (with paths, fills, images, text, etc)
// which is then passed to a renderer for rendering
scene: Scene,

// Our text state object
editor: text::Editor,
}

impl<'s> ApplicationHandler for SimpleVelloApp<'s> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let RenderState::Suspended(cached_window) = &mut self.state else {
return;
};

// Get the winit window cached in a previous Suspended event or else create a new window
let window = cached_window
.take()
.unwrap_or_else(|| create_winit_window(event_loop));

// Create a vello Surface
let size = window.inner_size();
let surface_future = self.context.create_surface(
window.clone(),
size.width,
size.height,
wgpu::PresentMode::AutoVsync,
);
let surface = pollster::block_on(surface_future).expect("Error creating surface");
self.editor.update_layout(size.width as _, 1.0);

// Create a vello Renderer for the surface (using its device id)
self.renderers
.resize_with(self.context.devices.len(), || None);
self.renderers[surface.dev_id]
.get_or_insert_with(|| create_vello_renderer(&self.context, &surface));

// Save the Window and Surface to a state variable
self.state = RenderState::Active(ActiveRenderState { window, surface });

event_loop.set_control_flow(ControlFlow::Poll);
}

fn suspended(&mut self, event_loop: &ActiveEventLoop) {
if let RenderState::Active(state) = &self.state {
self.state = RenderState::Suspended(Some(state.window.clone()));
}
event_loop.set_control_flow(ControlFlow::Wait);
}

fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
// Ignore the event (return from the function) if
// - we have no render_state
// - OR the window id of the event doesn't match the window id of our render_state
//
// Else extract a mutable reference to the render state from its containing option for use below
let render_state = match &mut self.state {
RenderState::Active(state) if state.window.id() == window_id => state,
_ => return,
};

self.editor.handle_event(&event);
render_state.window.request_redraw();
// render_state
// .window
// .set_cursor(winit::window::Cursor::Icon(winit::window::CursorIcon::Text));

match event {
// Exit the event loop when a close is requested (e.g. window's close button is pressed)
WindowEvent::CloseRequested => event_loop.exit(),

// Resize the surface when the window is resized
WindowEvent::Resized(size) => {
self.context
.resize_surface(&mut render_state.surface, size.width, size.height);
render_state.window.request_redraw();
self.editor.update_layout(size.width as _, 1.0);
}

// This is where all the rendering happens
WindowEvent::RedrawRequested => {
// Empty the scene of objects to draw. You could create a new Scene each time, but in this case
// the same Scene is reused so that the underlying memory allocation can also be reused.
self.scene.reset();

self.editor.draw(&mut self.scene);
// Re-add the objects to draw to the scene.
// add_shapes_to_scene(&mut self.scene);

// Get the RenderSurface (surface + config)
let surface = &render_state.surface;

// Get the window size
let width = surface.config.width;
let height = surface.config.height;

// Get a handle to the device
let device_handle = &self.context.devices[surface.dev_id];

// Get the surface's texture
let surface_texture = surface
.surface
.get_current_texture()
.expect("failed to get surface texture");

// Render to the surface's texture
self.renderers[surface.dev_id]
.as_mut()
.unwrap()
.render_to_surface(
&device_handle.device,
&device_handle.queue,
&self.scene,
&surface_texture,
&vello::RenderParams {
base_color: Color::rgb8(30, 30, 30), // Background color
width,
height,
antialiasing_method: AaConfig::Msaa16,
},
)
.expect("failed to render to surface");

// Queue the texture to be presented on the surface
surface_texture.present();

device_handle.device.poll(wgpu::Maintain::Poll);
}
_ => {}
}
}
}

fn main() -> Result<()> {
// Setup a bunch of state:
let mut app = SimpleVelloApp {
context: RenderContext::new(),
renderers: vec![],
state: RenderState::Suspended(None),
scene: Scene::new(),
editor: text::Editor::default(),
};

app.editor.set_text(text::LOREM);

// Create and run a winit event loop
let event_loop = EventLoop::new()?;
event_loop
.run_app(&mut app)
.expect("Couldn't run event loop");
Ok(())
}

/// Helper function that creates a Winit window and returns it (wrapped in an Arc for sharing between threads)
fn create_winit_window(event_loop: &ActiveEventLoop) -> Arc<Window> {
let attr = Window::default_attributes()
.with_inner_size(LogicalSize::new(1044, 800))
.with_resizable(true)
.with_title("Vello Text Editor");
Arc::new(event_loop.create_window(attr).unwrap())
}

/// Helper function that creates a vello `Renderer` for a given `RenderContext` and `RenderSurface`
fn create_vello_renderer(render_cx: &RenderContext, surface: &RenderSurface) -> Renderer {
Renderer::new(
&render_cx.devices[surface.dev_id].device,
RendererOptions {
surface_format: Some(surface.format),
use_cpu: false,
antialiasing_support: vello::AaSupport::all(),
num_init_threads: NonZeroUsize::new(1),
},
)
.expect("Couldn't create renderer")
}
Loading
Loading