Initial work toward VR support

This commit is contained in:
Patrick Walton 2019-03-13 16:32:39 -07:00
parent 9c404dfdc1
commit bb32777101
15 changed files with 432 additions and 189 deletions

13
Cargo.lock generated
View File

@ -186,7 +186,7 @@ dependencies = [
"arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -473,7 +473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -633,6 +633,7 @@ dependencies = [
"egl 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"jni 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_demo 0.1.0",
"pathfinder_geometry 0.3.0",
"pathfinder_gl 0.1.0",
@ -939,7 +940,7 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1073,7 +1074,7 @@ version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1224,7 +1225,7 @@ name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1412,7 +1413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum khronos 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c0711aaa80e6ba6eb1fa8978f1f46bfcb38ceb2f3f33f3736efbff39dac89f50"
"checksum khronos_api 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "037ab472c33f67b5fbd3e9163a2645319e5356fcd355efa6d4eb7fff4bbcb554"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122"
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
"checksum libflate 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "54d1ddf9c52870243c5689d7638d888331c1116aa5b398f3ba1acfa7d8758ca1"

View File

@ -10,6 +10,7 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@ -18,6 +19,8 @@ import android.view.View;
* status bar and navigation/system bar) with user interaction.
*/
public class PathfinderActivity extends AppCompatActivity {
private PathfinderDemoRenderer mRenderer;
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
@ -111,6 +114,7 @@ public class PathfinderActivity extends AppCompatActivity {
init();
}
@SuppressLint("ClickableViewAccessibility")
private void init() {
setContentView(R.layout.activity_pathfinder);
@ -118,6 +122,7 @@ public class PathfinderActivity extends AppCompatActivity {
mControlsView = findViewById(R.id.fullscreen_content_controls);
mContentView = findViewById(R.id.fullscreen_content);
/*
// Set up the user interaction to manually show or hide the system UI.
mContentView.setOnClickListener(new View.OnClickListener() {
@Override
@ -125,9 +130,30 @@ public class PathfinderActivity extends AppCompatActivity {
toggle();
}
});
*/
mContentView.setEGLContextClientVersion(3);
mContentView.setRenderer(new PathfinderDemoRenderer(getAssets()));
mRenderer = new PathfinderDemoRenderer(getAssets());
mContentView.setRenderer(mRenderer);
mContentView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
int x = Math.round(event.getX());
int y = Math.round(event.getY());
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.i("Pathfinder", "DOWN " + x + " " + y);
PathfinderDemoRenderer.pushMouseDownEvent(x, y);
break;
case MotionEvent.ACTION_MOVE:
Log.i("Pathfinder", "MOVE " + x + " " + y);
PathfinderDemoRenderer.pushMouseDraggedEvent(x, y);
break;
}
return true;
}
});
}
@Override

View File

@ -12,6 +12,9 @@ public class PathfinderDemoRenderer extends Object implements GLSurfaceView.Rend
private static native void init(PathfinderDemoResourceLoader resourceLoader);
private static native void runOnce();
public static native void pushMouseDownEvent(int x, int y);
public static native void pushMouseDraggedEvent(int x, int y);
static {
System.loadLibrary("pathfinder_android_demo");
}

View File

@ -3,6 +3,8 @@ package graphics.pathfinder.pathfinderdemo;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class PathfinderDemoSurfaceView extends GLSurfaceView {
public PathfinderDemoSurfaceView(Context context) {

View File

@ -11,6 +11,7 @@ crate_type = ["cdylib"]
egl = "0.2"
gl = "0.6"
jni = "0.11"
lazy_static = "1.3"
[dependencies.pathfinder_demo]
path = "../../common"

View File

@ -8,6 +8,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[macro_use]
extern crate lazy_static;
use jni::{JNIEnv, JavaVM};
use jni::objects::{GlobalRef, JByteBuffer, JClass, JObject, JValue};
use pathfinder_demo::DemoApp;
@ -16,9 +19,16 @@ use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader;
use std::cell::RefCell;
use std::ffi::CString;
use std::io::Error as IOError;
use std::mem;
use std::os::raw::c_void;
use std::path::PathBuf;
use std::sync::Mutex;
lazy_static! {
static ref EVENT_QUEUE: Mutex<Vec<Event>> = Mutex::new(vec![]);
}
thread_local! {
static DEMO_APP: RefCell<Option<DemoApp<WindowImpl>>> = RefCell::new(None);
@ -43,18 +53,31 @@ pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_runOnce(env: JNIEnv,
class: JClass) {
DEMO_APP.with(|demo_app| {
let mut event_queue = EVENT_QUEUE.lock().unwrap();
if let Some(ref mut demo_app) = *demo_app.borrow_mut() {
demo_app.run_once(vec![]);
demo_app.run_once(mem::replace(&mut *event_queue, vec![]));
}
});
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushMouseDown(env: JNIEnv,
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushMouseDownEvent(
env: JNIEnv,
class: JClass,
x: i32,
y: i32) {
EVENT_QUEUE.lock().unwrap().push(Event::MouseDown(Point2DI32::new(x, y)))
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushMouseDraggedEvent(
env: JNIEnv,
class: JClass,
x: i32,
y: i32) {
EVENT_QUEUE.lock().unwrap().push(Event::MouseDragged(Point2DI32::new(x, y)))
}
struct WindowImpl;
@ -70,11 +93,11 @@ impl Window for WindowImpl {
}
fn size(&self) -> Point2DI32 {
Point2DI32::new(1080, 1920)
Point2DI32::new(1920, 1080)
}
fn drawable_size(&self) -> Point2DI32 {
Point2DI32::new(1080, 1920)
Point2DI32::new(1920, 1080)
}
fn mouse_position(&self) -> Point2DI32 {

View File

@ -17,7 +17,7 @@ use clap::{App, Arg};
use image::ColorType;
use jemallocator;
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::rect::{RectF32, RectI32};
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32};
use pathfinder_geometry::color::ColorU;
@ -27,17 +27,18 @@ use pathfinder_gpu::{DepthFunc, DepthState, Device, Primitive, RenderState, Sten
use pathfinder_gpu::{StencilState, UniformData};
use pathfinder_renderer::builder::{RenderOptions, RenderTransform, SceneBuilder};
use pathfinder_renderer::gpu::renderer::Renderer;
use pathfinder_renderer::gpu_data::BuiltScene;
use pathfinder_renderer::gpu_data::{BuiltScene, Stats};
use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS};
use pathfinder_renderer::scene::Scene;
use pathfinder_renderer::z_buffer::ZBuffer;
use pathfinder_svg::BuiltSVG;
use pathfinder_ui::UIEvent;
use pathfinder_ui::{MousePosition, UIEvent};
use rayon::ThreadPoolBuilder;
use std::f32::consts::FRAC_PI_4;
use std::ffi::CString;
use std::fs::File;
use std::io::Read;
use std::iter;
use std::mem;
use std::panic;
use std::path::{Path, PathBuf};
@ -98,6 +99,7 @@ pub struct DemoApp<W> where W: Window {
dirty: bool,
expire_message_event_id: u32,
message_epoch: u32,
last_mouse_position: Point2DI32,
ui: DemoUI<GLDevice>,
scene_thread_proxy: SceneThreadProxy,
@ -120,20 +122,24 @@ impl<W> DemoApp<W> where W: Window {
let options = Options::get(resources);
let (window_size, drawable_size) = (window.size(), window.drawable_size());
let view_box_size = view_box_size(options.mode, &window);
let built_svg = load_scene(resources, &options.input_path);
let message = get_svg_building_message(&built_svg);
let scene_view_box = built_svg.scene.view_box;
let scene_is_monochrome = built_svg.scene.is_monochrome();
let renderer = Renderer::new(device, resources, drawable_size);
let renderer = Renderer::new(device,
resources,
RectI32::new(Point2DI32::default(), view_box_size),
drawable_size);
let scene_thread_proxy = SceneThreadProxy::new(built_svg.scene, options.clone());
scene_thread_proxy.set_drawable_size(window.drawable_size());
scene_thread_proxy.set_drawable_size(view_box_size);
let camera = if options.three_d {
Camera::new_3d(scene_view_box)
let camera = if options.mode == Mode::TwoD {
Camera::new_2d(scene_view_box, view_box_size)
} else {
Camera::new_2d(scene_view_box, drawable_size)
Camera::new_3d(scene_view_box)
};
let ground_program = GroundProgram::new(&renderer.device, resources);
@ -164,6 +170,7 @@ impl<W> DemoApp<W> where W: Window {
dirty: true,
expire_message_event_id,
message_epoch,
last_mouse_position: Point2DI32::default(),
ui,
scene_thread_proxy,
@ -180,31 +187,35 @@ impl<W> DemoApp<W> where W: Window {
self.build_scene();
// Handle events.
let ui_event = self.handle_events(events);
let ui_events = self.handle_events(events);
// Draw the scene.
let render_msg = self.scene_thread_proxy.receiver.recv().unwrap();
self.draw_scene(render_msg, ui_event);
self.draw_scene(render_msg, ui_events);
}
fn build_scene(&mut self) {
let drawable_size = self.window.drawable_size();
let view_box_size = view_box_size(self.ui.mode, &self.window);
let render_transform = match self.camera {
Camera::ThreeD { ref mut transform, ref mut velocity } => {
if transform.offset(*velocity) {
self.dirty = true;
}
let perspective = transform.to_perspective(drawable_size);
let perspective = transform.to_perspective(view_box_size);
RenderTransform::Perspective(perspective)
}
Camera::TwoD(transform) => RenderTransform::Transform2D(transform),
};
let count = if self.frame_counter == 0 { 2 } else { 1 };
for _ in 0..count {
let is_first_frame = self.frame_counter == 0;
let frame_count = if is_first_frame { 2 } else { 1 };
for _ in 0..frame_count {
let viewport_count = self.ui.mode.viewport_count();
let render_transforms = iter::repeat(render_transform.clone()).take(viewport_count)
.collect();
self.scene_thread_proxy.sender.send(MainToSceneMsg::Build(BuildOptions {
render_transform: render_transform.clone(),
render_transforms,
stem_darkening_font_size: if self.ui.stem_darkening_effect_enabled {
Some(APPROX_FONT_SIZE * self.scale_factor)
} else {
@ -213,13 +224,13 @@ impl<W> DemoApp<W> where W: Window {
})).unwrap();
}
if count == 2 {
if is_first_frame {
self.dirty = true;
}
}
fn handle_events(&mut self, events: Vec<Event>) -> UIEvent {
let mut ui_event = UIEvent::None;
fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> {
let mut ui_events = vec![];
self.dirty = false;
for event in events {
@ -230,25 +241,29 @@ impl<W> DemoApp<W> where W: Window {
self.dirty = true;
}
Event::WindowResized => {
self.scene_thread_proxy.set_drawable_size(self.window.drawable_size());
let view_box_size = view_box_size(self.ui.mode, &self.window);
self.scene_thread_proxy.set_drawable_size(view_box_size);
self.renderer.set_main_framebuffer_size(self.window.drawable_size());
self.dirty = true;
}
Event::MouseDown(position) => {
ui_event = UIEvent::MouseDown(position.scale(self.scale_factor as i32));
Event::MouseDown(new_position) => {
let mouse_position = self.process_mouse_position(new_position);
ui_events.push(UIEvent::MouseDown(mouse_position));
}
Event::MouseMoved { relative_position, .. } if self.mouselook_enabled => {
Event::MouseMoved(new_position) if self.mouselook_enabled => {
let mouse_position = self.process_mouse_position(new_position);
if let Camera::ThreeD { ref mut transform, .. } = self.camera {
let rotation = relative_position.to_f32().scale(MOUSELOOK_ROTATION_SPEED);
let rotation = mouse_position.relative
.to_f32()
.scale(MOUSELOOK_ROTATION_SPEED);
transform.yaw += rotation.x();
transform.pitch += rotation.y();
self.dirty = true;
}
}
Event::MouseDragged { position, relative_position } => {
let absolute_position = position.scale(self.scale_factor as i32);
let relative_position = relative_position.scale(self.scale_factor as i32);
ui_event = UIEvent::MouseDragged { absolute_position, relative_position };
Event::MouseDragged(new_position) => {
let mouse_position = self.process_mouse_position(new_position);
ui_events.push(UIEvent::MouseDragged(mouse_position));
self.dirty = true;
}
Event::Zoom(d_dist) => {
@ -312,34 +327,58 @@ impl<W> DemoApp<W> where W: Window {
}
}
ui_event
ui_events
}
fn draw_scene(&mut self, render_msg: SceneToMainMsg, mut ui_event: UIEvent) {
let SceneToMainMsg::Render {
built_scene,
transform: render_transform,
tile_time,
} = render_msg;
fn process_mouse_position(&mut self, new_position: Point2DI32) -> MousePosition {
let absolute = new_position.scale(self.scale_factor as i32);
let relative = absolute - self.last_mouse_position;
self.last_mouse_position = absolute;
MousePosition { absolute, relative }
}
fn draw_scene(&mut self, render_msg: SceneToMainMsg, mut ui_events: Vec<UIEvent>) {
self.renderer.device.clear(Some(self.background_color().to_f32().0), Some(1.0), Some(0));
self.draw_environment(&render_transform);
self.render_vector_scene(&built_scene);
let SceneToMainMsg::Render { render_scenes, tile_time } = render_msg;
let mut render_stats = None;
for (viewport_index, render_scene) in render_scenes.iter().enumerate() {
self.draw_environment(viewport_index, &render_scene.transform);
self.render_vector_scene(viewport_index, &render_scene.built_scene);
match render_stats {
None => {
render_stats = Some(RenderStats {
rendering_time: self.renderer.shift_timer_query(),
stats: render_scene.built_scene.stats(),
})
}
Some(ref mut render_stats) => {
render_stats.stats = render_stats.stats + render_scene.built_scene.stats()
}
}
}
let drawable_size = self.window.drawable_size();
self.renderer.set_viewport(RectI32::new(Point2DI32::default(), drawable_size));
if self.pending_screenshot_path.is_some() {
self.take_screenshot();
}
let rendering_time = self.renderer.shift_timer_query();
let stats = built_scene.stats();
self.renderer.debug_ui.add_sample(stats, tile_time, rendering_time);
self.renderer.debug_ui.draw(&self.renderer.device);
if !ui_event.is_none() {
self.dirty = true;
if let Some(render_stats) = render_stats {
self.renderer.debug_ui.add_sample(render_stats.stats,
tile_time,
render_stats.rendering_time);
self.renderer.draw_debug_ui();
}
for ui_event in &ui_events {
self.dirty = true;
self.renderer.debug_ui.ui.event_queue.push(*ui_event);
}
self.renderer.debug_ui.ui.event = ui_event;
self.renderer.debug_ui.ui.mouse_position = get_mouse_position(&self.window,
self.scale_factor);
self.ui.show_text_effects = self.scene_is_monochrome;
@ -350,39 +389,43 @@ impl<W> DemoApp<W> where W: Window {
&mut self.renderer.debug_ui,
&mut ui_action);
ui_event = mem::replace(&mut self.renderer.debug_ui.ui.event, UIEvent::None);
ui_events = self.renderer.debug_ui.ui.event_queue.drain();
self.handle_ui_action(&mut ui_action);
// Switch camera mode (2D/3D) if requested.
//
// FIXME(pcwalton): This mess should really be an MVC setup.
match (&self.camera, self.ui.three_d_enabled) {
(&Camera::TwoD { .. }, true) => self.camera = Camera::new_3d(self.scene_view_box),
(&Camera::ThreeD { .. }, false) => {
match (&self.camera, self.ui.mode) {
(&Camera::TwoD { .. }, Mode::ThreeD) | (&Camera::TwoD { .. }, Mode::VR) => {
self.camera = Camera::new_3d(self.scene_view_box);
}
(&Camera::ThreeD { .. }, Mode::TwoD) => {
let drawable_size = self.window.drawable_size();
self.camera = Camera::new_2d(self.scene_view_box, drawable_size);
}
_ => {}
}
for ui_event in ui_events {
match ui_event {
UIEvent::MouseDown(_) if self.camera.is_3d() => {
// If nothing handled the mouse-down event, toggle mouselook.
self.mouselook_enabled = !self.mouselook_enabled;
}
UIEvent::MouseDragged { relative_position, .. } => {
UIEvent::MouseDragged(position) => {
if let Camera::TwoD(ref mut transform) = self.camera {
*transform = transform.post_translate(relative_position.to_f32());
*transform = transform.post_translate(position.relative.to_f32());
}
}
_ => {}
}
}
self.window.present();
self.frame_counter += 1;
}
fn draw_environment(&self, render_transform: &RenderTransform) {
fn draw_environment(&self, viewport_index: usize, render_transform: &RenderTransform) {
let perspective = match *render_transform {
RenderTransform::Transform2D(..) => return,
RenderTransform::Perspective(perspective) => perspective,
@ -449,7 +492,12 @@ impl<W> DemoApp<W> where W: Window {
});
}
fn render_vector_scene(&mut self, built_scene: &BuiltScene) {
fn render_vector_scene(&mut self, viewport_index: usize, built_scene: &BuiltScene) {
let view_box_size = view_box_size(self.ui.mode, &self.window);
let viewport_origin_x = viewport_index as i32 * view_box_size.x();
let viewport = RectI32::new(Point2DI32::new(viewport_origin_x, 0), view_box_size);
self.renderer.set_viewport(viewport);
if self.ui.gamma_correction_effect_enabled {
self.renderer.enable_gamma_correction(self.background_color());
} else {
@ -462,10 +510,10 @@ impl<W> DemoApp<W> where W: Window {
self.renderer.disable_subpixel_aa();
}
if self.ui.three_d_enabled {
self.renderer.enable_depth();
} else {
if self.ui.mode == Mode::TwoD {
self.renderer.disable_depth();
} else {
self.renderer.enable_depth();
}
self.renderer.render_scene(&built_scene);
@ -485,10 +533,10 @@ impl<W> DemoApp<W> where W: Window {
self.scene_thread_proxy.set_drawable_size(self.window.drawable_size());
let drawable_size = self.window.drawable_size();
self.camera = if self.ui.three_d_enabled {
Camera::new_3d(built_svg.scene.view_box)
} else {
self.camera = if self.ui.mode == Mode::TwoD {
Camera::new_2d(built_svg.scene.view_box, drawable_size)
} else {
Camera::new_3d(built_svg.scene.view_box)
};
self.scene_thread_proxy.load_scene(built_svg.scene);
@ -546,6 +594,7 @@ impl<W> DemoApp<W> where W: Window {
fn background_color(&self) -> ColorU {
if self.ui.dark_background_enabled { DARK_BG_COLOR } else { LIGHT_BG_COLOR }
}
}
struct SceneThreadProxy {
@ -593,15 +642,18 @@ impl SceneThread {
self.scene.view_box = RectF32::new(Point2DF32::default(), size.to_f32());
}
MainToSceneMsg::Build(build_options) => {
let render_transform = build_options.render_transform.clone();
let start_time = Instant::now();
let built_scene = build_scene(&self.scene, build_options, self.options.jobs);
let render_scenes = build_options.render_transforms
.iter()
.map(|render_transform| {
let built_scene = build_scene(&self.scene,
&build_options,
(*render_transform).clone(),
self.options.jobs);
RenderScene { built_scene, transform: (*render_transform).clone() }
}).collect();
let tile_time = Instant::now() - start_time;
self.sender.send(SceneToMainMsg::Render {
built_scene,
transform: render_transform,
tile_time,
}).unwrap();
self.sender.send(SceneToMainMsg::Render { render_scenes, tile_time }).unwrap();
}
}
}
@ -615,18 +667,23 @@ enum MainToSceneMsg {
}
struct BuildOptions {
render_transform: RenderTransform,
render_transforms: Vec<RenderTransform>,
stem_darkening_font_size: Option<f32>,
}
enum SceneToMainMsg {
Render { built_scene: BuiltScene, transform: RenderTransform, tile_time: Duration }
Render { render_scenes: Vec<RenderScene>, tile_time: Duration }
}
pub struct RenderScene {
built_scene: BuiltScene,
transform: RenderTransform,
}
#[derive(Clone)]
pub struct Options {
jobs: Option<usize>,
three_d: bool,
mode: Mode,
input_path: Option<PathBuf>,
}
@ -641,14 +698,22 @@ impl Options {
.takes_value(true)
.help("Number of threads to use"),
)
.arg(Arg::with_name("3d").short("3").long("3d").help("Run in 3D"))
.arg(Arg::with_name("3d").short("3").long("3d").help("Run in 3D").conflicts_with("vr"))
.arg(Arg::with_name("vr").short("V").long("vr").help("Run in VR").conflicts_with("3d"))
.arg(Arg::with_name("INPUT").help("Path to the SVG file to render").index(1))
.get_matches();
let jobs: Option<usize> = matches
.value_of("jobs")
.map(|string| string.parse().unwrap());
let three_d = matches.is_present("3d");
let mode = if matches.is_present("3d") {
Mode::ThreeD
} else if matches.is_present("vr") {
Mode::VR
} else {
Mode::TwoD
};
let input_path = matches.value_of("INPUT").map(PathBuf::from);
@ -659,10 +724,29 @@ impl Options {
}
thread_pool_builder.build_global().unwrap();
Options { jobs, three_d, input_path }
Options { jobs, mode, input_path }
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Mode {
TwoD = 0,
ThreeD = 1,
VR = 2,
}
impl Mode {
fn viewport_count(self) -> usize {
match self { Mode::TwoD | Mode::ThreeD => 1, Mode::VR => 2 }
}
}
#[derive(Clone, Copy)]
struct RenderStats {
rendering_time: Option<Duration>,
stats: Stats,
}
fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &Option<PathBuf>) -> BuiltSVG {
let mut data;
match *input_path {
@ -680,11 +764,15 @@ fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &Option<PathBuf>
BuiltSVG::from_tree(Tree::from_data(&data, &UsvgOptions::default()).unwrap())
}
fn build_scene(scene: &Scene, build_options: BuildOptions, jobs: Option<usize>) -> BuiltScene {
fn build_scene(scene: &Scene,
build_options: &BuildOptions,
render_transform: RenderTransform,
jobs: Option<usize>)
-> BuiltScene {
let z_buffer = ZBuffer::new(scene.view_box);
let render_options = RenderOptions {
transform: build_options.render_transform,
transform: render_transform,
dilation: match build_options.stem_darkening_font_size {
None => Point2DF32::default(),
Some(font_size) => {
@ -835,3 +923,11 @@ fn emit_message<W>(ui: &mut DemoUI<GLDevice>,
W::push_user_event(expire_message_event_id, expected_epoch);
});
}
fn view_box_size<W>(mode: Mode, window: &W) -> Point2DI32 where W: Window {
let window_drawable_size = window.drawable_size();
match mode {
Mode::TwoD | Mode::ThreeD => window_drawable_size,
Mode::VR => Point2DI32::new(window_drawable_size.x() / 2, window_drawable_size.y()),
}
}

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::Options;
use crate::{Mode, Options};
use crate::window::Window;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
@ -16,7 +16,7 @@ use pathfinder_gpu::Device;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_renderer::gpu::debug::DebugUI;
use pathfinder_ui::{BUTTON_HEIGHT, BUTTON_TEXT_OFFSET, BUTTON_WIDTH, FONT_ASCENT, PADDING};
use pathfinder_ui::{SWITCH_SIZE, TEXT_COLOR, TOOLTIP_HEIGHT, WINDOW_COLOR};
use pathfinder_ui::{TEXT_COLOR, TOOLTIP_HEIGHT, WINDOW_COLOR};
use std::f32::consts::PI;
use std::path::PathBuf;
@ -56,7 +56,7 @@ pub struct DemoUI<D> where D: Device {
// FIXME(pcwalton): Factor the below out into a model class.
pub three_d_enabled: bool,
pub mode: Mode,
pub dark_background_enabled: bool,
pub gamma_correction_effect_enabled: bool,
pub stem_darkening_effect_enabled: bool,
@ -90,7 +90,7 @@ impl<D> DemoUI<D> where D: Device {
effects_panel_visible: false,
rotate_panel_visible: false,
three_d_enabled: options.three_d,
mode: options.mode,
dark_background_enabled: false,
gamma_correction_effect_enabled: false,
stem_darkening_effect_enabled: false,
@ -121,7 +121,6 @@ impl<D> DemoUI<D> where D: Device {
let mut position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT);
let button_size = Point2DI32::new(BUTTON_WIDTH, BUTTON_HEIGHT);
let switch_size = Point2DI32::new(SWITCH_SIZE, BUTTON_HEIGHT);
// Draw text effects button.
if self.show_text_effects {
@ -158,14 +157,24 @@ impl<D> DemoUI<D> where D: Device {
debug_ui.ui.draw_tooltip(device, "Take Screenshot", RectI32::new(position, button_size));
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
// Draw 3D switch.
self.three_d_enabled = debug_ui.ui.draw_text_switch(device,
// Draw mode switch.
let new_mode = debug_ui.ui.draw_text_switch(device,
position,
"2D",
"3D",
self.three_d_enabled);
debug_ui.ui.draw_tooltip(device, "2D/3D Mode", RectI32::new(position, switch_size));
position += Point2DI32::new(SWITCH_SIZE + PADDING, 0);
&["2D", "3D", "VR"],
self.mode as u8);
if new_mode != self.mode as u8 {
self.mode = match new_mode {
0 => Mode::TwoD,
1 => Mode::ThreeD,
_ => Mode::VR,
};
}
let mode_switch_width = debug_ui.ui.measure_switch(3);
let mode_switch_size = Point2DI32::new(mode_switch_width, BUTTON_HEIGHT);
debug_ui.ui.draw_tooltip(device,
"2D/3D/VR Mode",
RectI32::new(position, mode_switch_size));
position += Point2DI32::new(mode_switch_width + PADDING, 0);
// Draw background switch.
self.dark_background_enabled = debug_ui.ui.draw_image_switch(device,
@ -173,14 +182,18 @@ impl<D> DemoUI<D> where D: Device {
&self.bg_light_texture,
&self.bg_dark_texture,
self.dark_background_enabled);
debug_ui.ui.draw_tooltip(device, "Background Color", RectI32::new(position, switch_size));
position += Point2DI32::new(SWITCH_SIZE + PADDING, 0);
let background_color_switch_width = debug_ui.ui.measure_switch(2);
let background_color_switch_size = Point2DI32::new(background_color_switch_width,
BUTTON_HEIGHT);
let background_color_switch_rect = RectI32::new(position, background_color_switch_size);
debug_ui.ui.draw_tooltip(device, "Background Color", background_color_switch_rect);
position += Point2DI32::new(background_color_switch_width + PADDING, 0);
// Draw effects panel, if necessary.
self.draw_effects_panel(device, debug_ui);
// Draw rotate and zoom buttons, if applicable.
if self.three_d_enabled {
if self.mode != Mode::TwoD {
return;
}
@ -281,7 +294,7 @@ impl<D> DemoUI<D> where D: Device {
let widget_rect = RectI32::new(Point2DI32::new(widget_x, widget_y),
Point2DI32::new(SLIDER_WIDTH, SLIDER_KNOB_HEIGHT));
if let Some(position) = debug_ui.ui
.event
.event_queue
.handle_mouse_down_or_dragged_in_rect(widget_rect) {
self.rotation = position.x();
*action = UIAction::Rotate(self.rotation());
@ -313,10 +326,11 @@ impl<D> DemoUI<D> where D: Device {
let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index;
debug_ui.ui.draw_text(device, text, Point2DI32::new(text_x, text_y), false);
let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (SWITCH_SIZE + PADDING);
let switch_width = debug_ui.ui.measure_switch(2);
let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING);
let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index;
let switch_position = Point2DI32::new(switch_x, switch_y);
debug_ui.ui.draw_text_switch(device, switch_position, "Off", "On", value)
debug_ui.ui.draw_text_switch(device, switch_position, &["Off", "On"], value as u8) != 0
}
}

View File

@ -36,8 +36,8 @@ pub enum Event {
KeyDown(Keycode),
KeyUp(Keycode),
MouseDown(Point2DI32),
MouseMoved { position: Point2DI32, relative_position: Point2DI32 },
MouseDragged { position: Point2DI32, relative_position: Point2DI32 },
MouseMoved(Point2DI32),
MouseDragged(Point2DI32),
Zoom(f32),
User { message_type: u32, message_data: u32 },
}

View File

@ -167,13 +167,12 @@ impl WindowImpl {
SDLEvent::MouseButtonDown { x, y, .. } => {
Some(Event::MouseDown(Point2DI32::new(x, y)))
}
SDLEvent::MouseMotion { x, y, xrel, yrel, mousestate, .. } => {
SDLEvent::MouseMotion { x, y, mousestate, .. } => {
let position = Point2DI32::new(x, y);
let relative_position = Point2DI32::new(xrel, yrel);
if mousestate.left() {
Some(Event::MouseDragged { position, relative_position })
Some(Event::MouseDragged(position))
} else {
Some(Event::MouseMoved { position, relative_position })
Some(Event::MouseMoved(position))
}
}
SDLEvent::Quit { .. } => Some(Event::Quit),

View File

@ -12,6 +12,7 @@
use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, Device, Primitive};
use pathfinder_gpu::{RenderState, ShaderKind, StencilFunc, TextureFormat};
use pathfinder_gpu::{UniformData, VertexAttrType};
@ -584,10 +585,13 @@ impl Device for GLDevice {
}
#[inline]
fn bind_default_framebuffer(&self, size: Point2DI32) {
fn bind_default_framebuffer(&self, viewport: RectI32) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0); ck();
gl::Viewport(0, 0, size.x(), size.y()); ck();
gl::Viewport(viewport.origin().x(),
viewport.origin().y(),
viewport.size().x(),
viewport.size().y()); ck();
}
}

View File

@ -13,6 +13,7 @@
use crate::resources::ResourceLoader;
use image::ImageFormat;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_simd::default::F32x4;
use std::env;
use std::fs::File;
@ -91,7 +92,7 @@ pub trait Device {
// TODO(pcwalton): Go bindless...
fn bind_vertex_array(&self, vertex_array: &Self::VertexArray);
fn bind_buffer(&self, buffer: &Self::Buffer, target: BufferTarget);
fn bind_default_framebuffer(&self, size: Point2DI32);
fn bind_default_framebuffer(&self, viewport: RectI32);
fn bind_framebuffer(&self, framebuffer: &Self::Framebuffer);
fn bind_texture(&self, texture: &Self::Texture, unit: u32);

View File

@ -14,6 +14,7 @@ use crate::post::DefringingKernel;
use crate::scene::ObjectShader;
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32};
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::color::ColorU;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, DepthState, Device};
@ -68,13 +69,17 @@ pub struct Renderer<D> where D: Device {
pub debug_ui: DebugUI<D>,
// Extra info
viewport: RectI32,
main_framebuffer_size: Point2DI32,
postprocess_options: PostprocessOptions,
use_depth: bool,
}
impl<D> Renderer<D> where D: Device {
pub fn new(device: D, resources: &dyn ResourceLoader, main_framebuffer_size: Point2DI32)
pub fn new(device: D,
resources: &dyn ResourceLoader,
viewport: RectI32,
main_framebuffer_size: Point2DI32)
-> Renderer<D> {
let fill_program = FillProgram::new(&device, resources);
let solid_tile_program = SolidTileProgram::new(&device, resources);
@ -144,6 +149,7 @@ impl<D> Renderer<D> where D: Device {
debug_ui,
viewport,
main_framebuffer_size,
postprocess_options: PostprocessOptions::default(),
use_depth: false,
@ -181,6 +187,11 @@ impl<D> Renderer<D> where D: Device {
self.pending_timer_queries.push_back(timer_query);
}
pub fn draw_debug_ui(&self) {
self.device.bind_default_framebuffer(self.viewport);
self.debug_ui.draw(&self.device);
}
pub fn shift_timer_query(&mut self) -> Option<Duration> {
let query = self.pending_timer_queries.front()?;
if !self.device.timer_query_is_available(&query) {
@ -192,9 +203,13 @@ impl<D> Renderer<D> where D: Device {
Some(result)
}
#[inline]
pub fn set_viewport(&mut self, new_viewport: RectI32) {
self.viewport = new_viewport;
}
#[inline]
pub fn set_main_framebuffer_size(&mut self, new_framebuffer_size: Point2DI32) {
self.main_framebuffer_size = new_framebuffer_size;
self.debug_ui.ui.set_framebuffer_size(new_framebuffer_size);
}
@ -299,7 +314,7 @@ impl<D> Renderer<D> where D: Device {
self.device.bind_vertex_array(&self.mask_tile_vertex_array.vertex_array);
self.device.use_program(&self.mask_tile_program.program);
self.device.set_uniform(&self.mask_tile_program.framebuffer_size_uniform,
UniformData::Vec2(self.main_framebuffer_size.0.to_f32x4()));
UniformData::Vec2(self.viewport.size().to_f32().0));
self.device.set_uniform(&self.mask_tile_program.tile_size_uniform,
UniformData::Vec2(I32x4::new(TILE_WIDTH as i32,
TILE_HEIGHT as i32,
@ -339,7 +354,7 @@ impl<D> Renderer<D> where D: Device {
self.device.bind_vertex_array(&self.solid_tile_vertex_array.vertex_array);
self.device.use_program(&self.solid_tile_program.program);
self.device.set_uniform(&self.solid_tile_program.framebuffer_size_uniform,
UniformData::Vec2(self.main_framebuffer_size.0.to_f32x4()));
UniformData::Vec2(self.viewport.size().0.to_f32x4()));
self.device.set_uniform(&self.solid_tile_program.tile_size_uniform,
UniformData::Vec2(I32x4::new(TILE_WIDTH as i32,
TILE_HEIGHT as i32,
@ -365,12 +380,12 @@ impl<D> Renderer<D> where D: Device {
}
fn postprocess(&mut self) {
self.device.bind_default_framebuffer(self.main_framebuffer_size);
self.device.bind_default_framebuffer(self.viewport);
self.device.bind_vertex_array(&self.postprocess_vertex_array.vertex_array);
self.device.use_program(&self.postprocess_program.program);
self.device.set_uniform(&self.postprocess_program.framebuffer_size_uniform,
UniformData::Vec2(self.main_framebuffer_size.to_f32().0));
UniformData::Vec2(self.viewport.size().to_f32().0));
match self.postprocess_options.defringing_kernel {
Some(ref kernel) => {
self.device.set_uniform(&self.postprocess_program.kernel_uniform,
@ -434,7 +449,7 @@ impl<D> Renderer<D> where D: Device {
if self.postprocessing_needed() {
self.device.bind_framebuffer(self.postprocess_source_framebuffer.as_ref().unwrap());
} else {
self.device.bind_default_framebuffer(self.main_framebuffer_size);
self.device.bind_default_framebuffer(self.viewport);
}
}
@ -447,10 +462,10 @@ impl<D> Renderer<D> where D: Device {
match self.postprocess_source_framebuffer {
Some(ref framebuffer) if
self.device.texture_size(self.device.framebuffer_texture(framebuffer)) ==
self.main_framebuffer_size => {}
self.viewport.size() => {}
_ => {
let texture = self.device.create_texture(TextureFormat::RGBA8,
self.main_framebuffer_size);
self.viewport.size());
self.postprocess_source_framebuffer = Some(self.device.create_framebuffer(texture))
}
};

View File

@ -18,6 +18,7 @@ use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32};
use pathfinder_geometry::basic::rect::{RectF32, RectI32};
use pathfinder_geometry::util;
use pathfinder_simd::default::{F32x4, I32x4};
use std::ops::Add;
#[derive(Debug)]
pub struct BuiltObject {
@ -306,3 +307,15 @@ impl TileObjectPrimitive {
}
}
}
impl Add<Stats> for Stats {
type Output = Stats;
fn add(self, other: Stats) -> Stats {
Stats {
object_count: other.object_count,
solid_tile_count: other.solid_tile_count,
mask_tile_count: other.mask_tile_count,
fill_count: other.fill_count,
}
}
}

View File

@ -25,8 +25,7 @@ use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, Device, Primiti
use pathfinder_gpu::{UniformData, VertexAttrType};
use pathfinder_simd::default::F32x4;
use serde_json;
use std::fs::File;
use std::io::BufReader;
use std::mem;
pub const PADDING: i32 = 12;
@ -37,8 +36,6 @@ pub const BUTTON_WIDTH: i32 = PADDING * 2 + ICON_SIZE;
pub const BUTTON_HEIGHT: i32 = PADDING * 2 + ICON_SIZE;
pub const BUTTON_TEXT_OFFSET: i32 = PADDING + 36;
pub const SWITCH_SIZE: i32 = SWITCH_HALF_SIZE * 2 + 1;
pub const TOOLTIP_HEIGHT: i32 = FONT_ASCENT + PADDING * 2;
const DEBUG_TEXTURE_VERTEX_SIZE: usize = 8;
@ -46,7 +43,7 @@ const DEBUG_SOLID_VERTEX_SIZE: usize = 4;
const ICON_SIZE: i32 = 48;
const SWITCH_HALF_SIZE: i32 = 96;
const SWITCH_SEGMENT_SIZE: i32 = 96;
pub static TEXT_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
pub static WINDOW_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 - 90 };
@ -67,7 +64,7 @@ static RECT_LINE_INDICES: [u32; 8] = [0, 1, 1, 2, 2, 3, 3, 0];
static OUTLINE_RECT_LINE_INDICES: [u32; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
pub struct UI<D> where D: Device {
pub event: UIEvent,
pub event_queue: UIEventQueue,
pub mouse_position: Point2DF32,
framebuffer_size: Point2DI32,
@ -98,7 +95,7 @@ impl<D> UI<D> where D: Device {
CORNER_OUTLINE_PNG_NAME);
UI {
event: UIEvent::None,
event_queue: UIEventQueue::new(),
mouse_position: Point2DF32::default(),
framebuffer_size,
@ -251,6 +248,11 @@ impl<D> UI<D> where D: Device {
next
}
#[inline]
pub fn measure_switch(&self, segment_count: u8) -> i32 {
SWITCH_SEGMENT_SIZE * segment_count as i32 + (segment_count - 1) as i32
}
pub fn draw_solid_rounded_rect(&self, device: &D, rect: RectI32, color: ColorU) {
let corner_texture = self.corner_texture(true);
let corner_rects = CornerRects::new(device, rect, corner_texture);
@ -311,6 +313,13 @@ impl<D> UI<D> where D: Device {
self.draw_solid_rects_with_vertex_data(device, &vertex_data, index_data, color, false);
}
// TODO(pcwalton): `LineSegmentI32`.
fn draw_line(&self, device: &D, from: Point2DI32, to: Point2DI32, color: ColorU) {
let vertex_data = vec![DebugSolidVertex::new(from), DebugSolidVertex::new(to)];
self.draw_solid_rects_with_vertex_data(device, &vertex_data, &[0, 1], color, false);
}
fn draw_rounded_rect_corners(&self,
device: &D,
color: ColorU,
@ -409,30 +418,32 @@ impl<D> UI<D> where D: Device {
origin + Point2DI32::new(PADDING, PADDING),
texture,
BUTTON_ICON_COLOR);
self.event.handle_mouse_down_in_rect(button_rect).is_some()
self.event_queue.handle_mouse_down_in_rect(button_rect).is_some()
}
pub fn draw_text_switch(&mut self,
device: &D,
origin: Point2DI32,
off_text: &str,
on_text: &str,
mut value: bool)
-> bool {
value = self.draw_switch(device, origin, value);
mut origin: Point2DI32,
segment_labels: &[&str],
mut value: u8)
-> u8 {
value = self.draw_switch(device, origin, value, segment_labels.len() as u8);
let off_size = self.measure_text(off_text);
let on_size = self.measure_text(on_text);
let off_offset = SWITCH_HALF_SIZE / 2 - off_size / 2;
let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - on_size / 2;
let text_top = BUTTON_TEXT_OFFSET;
self.draw_text(device, off_text, origin + Point2DI32::new(off_offset, text_top), !value);
self.draw_text(device, on_text, origin + Point2DI32::new(on_offset, text_top), value);
origin = origin + Point2DI32::new(0, BUTTON_TEXT_OFFSET);
for (segment_index, segment_label) in segment_labels.iter().enumerate() {
let label_width = self.measure_text(segment_label);
let offset = SWITCH_SEGMENT_SIZE / 2 - label_width / 2;
self.draw_text(device,
segment_label,
origin + Point2DI32::new(offset, 0),
segment_index as u8 == value);
origin += Point2DI32::new(SWITCH_SEGMENT_SIZE + 1, 0);
}
value
}
/// TODO(pcwalton): Support switches with more than two segments.
pub fn draw_image_switch(&mut self,
device: &D,
origin: Point2DI32,
@ -440,10 +451,10 @@ impl<D> UI<D> where D: Device {
on_texture: &D::Texture,
mut value: bool)
-> bool {
value = self.draw_switch(device, origin, value);
value = self.draw_switch(device, origin, value as u8, 2) != 0;
let off_offset = SWITCH_HALF_SIZE / 2 - device.texture_size(off_texture).x() / 2;
let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 -
let off_offset = SWITCH_SEGMENT_SIZE / 2 - device.texture_size(off_texture).x() / 2;
let on_offset = SWITCH_SEGMENT_SIZE + SWITCH_SEGMENT_SIZE / 2 -
device.texture_size(on_texture).x() / 2;
let off_color = if !value { WINDOW_COLOR } else { TEXT_COLOR };
@ -461,24 +472,35 @@ impl<D> UI<D> where D: Device {
value
}
fn draw_switch(&mut self, device: &D, origin: Point2DI32, mut value: bool) -> bool {
let widget_rect = RectI32::new(origin, Point2DI32::new(SWITCH_SIZE, BUTTON_HEIGHT));
if self.event.handle_mouse_down_in_rect(widget_rect).is_some() {
value = !value;
fn draw_switch(&mut self, device: &D, origin: Point2DI32, mut value: u8, segment_count: u8)
-> u8 {
let widget_width = self.measure_switch(segment_count);
let widget_rect = RectI32::new(origin, Point2DI32::new(widget_width, BUTTON_HEIGHT));
if let Some(position) = self.event_queue.handle_mouse_down_in_rect(widget_rect) {
let segment = (position.x() / (SWITCH_SEGMENT_SIZE + 1)) as u8;
value = segment.min(segment_count - 1);
}
self.draw_solid_rounded_rect(device, widget_rect, WINDOW_COLOR);
self.draw_rounded_rect_outline(device, widget_rect, OUTLINE_COLOR);
let highlight_size = Point2DI32::new(SWITCH_HALF_SIZE, BUTTON_HEIGHT);
if !value {
self.draw_solid_rounded_rect(device, RectI32::new(origin, highlight_size), TEXT_COLOR);
} else {
let x_offset = SWITCH_HALF_SIZE + 1;
let highlight_size = Point2DI32::new(SWITCH_SEGMENT_SIZE, BUTTON_HEIGHT);
let x_offset = value as i32 * SWITCH_SEGMENT_SIZE + (value as i32 - 1);
self.draw_solid_rounded_rect(device,
RectI32::new(origin + Point2DI32::new(x_offset, 0),
highlight_size),
TEXT_COLOR);
let mut segment_origin = origin + Point2DI32::new(SWITCH_SEGMENT_SIZE + 1, 0);
for next_segment_index in 1..segment_count {
let prev_segment_index = next_segment_index - 1;
if value != prev_segment_index && value != next_segment_index {
self.draw_line(device,
segment_origin,
segment_origin + Point2DI32::new(0, BUTTON_HEIGHT),
TEXT_COLOR);
}
segment_origin = segment_origin + Point2DI32::new(SWITCH_SEGMENT_SIZE + 1, 0);
}
value
@ -666,40 +688,63 @@ fn set_color_uniform<D>(device: &D, uniform: &D::Uniform, color: ColorU) where D
device.set_uniform(uniform, UniformData::Vec4(color * F32x4::splat(1.0 / 255.0)));
}
#[derive(Clone, Copy)]
pub enum UIEvent {
None,
MouseDown(Point2DI32),
MouseDragged {
absolute_position: Point2DI32,
relative_position: Point2DI32,
}
MouseDown(MousePosition),
MouseDragged(MousePosition),
}
impl UIEvent {
pub fn is_none(&self) -> bool {
match *self { UIEvent::None => true, _ => false }
pub struct UIEventQueue {
events: Vec<UIEvent>,
}
impl UIEventQueue {
fn new() -> UIEventQueue {
UIEventQueue { events: vec![] }
}
pub fn push(&mut self, event: UIEvent) {
self.events.push(event);
}
pub fn drain(&mut self) -> Vec<UIEvent> {
mem::replace(&mut self.events, vec![])
}
fn handle_mouse_down_in_rect(&mut self, rect: RectI32) -> Option<Point2DI32> {
if let UIEvent::MouseDown(point) = *self {
if rect.contains_point(point) {
*self = UIEvent::None;
return Some(point - rect.origin());
let (mut remaining_events, mut result) = (vec![], None);
for event in self.events.drain(..) {
match event {
UIEvent::MouseDown(position) if rect.contains_point(position.absolute) => {
result = Some(position.absolute - rect.origin());
}
event => remaining_events.push(event),
}
}
None
self.events = remaining_events;
result
}
pub fn handle_mouse_down_or_dragged_in_rect(&mut self, rect: RectI32) -> Option<Point2DI32> {
match *self {
UIEvent::MouseDown(point) | UIEvent::MouseDragged { absolute_position: point, .. }
if rect.contains_point(point) => {
*self = UIEvent::None;
Some(point - rect.origin())
let (mut remaining_events, mut result) = (vec![], None);
for event in self.events.drain(..) {
match event {
UIEvent::MouseDown(position) | UIEvent::MouseDragged(position) if
rect.contains_point(position.absolute) => {
result = Some(position.absolute - rect.origin());
}
_ => None,
event => remaining_events.push(event),
}
}
self.events = remaining_events;
result
}
}
#[derive(Clone, Copy)]
pub struct MousePosition {
pub absolute: Point2DI32,
pub relative: Point2DI32,
}
#[derive(Deserialize)]