Allow opening new SVGs in the Android port

This commit is contained in:
Patrick Walton 2019-03-18 18:13:13 -07:00
parent 49660a6b31
commit 9025189650
21 changed files with 322 additions and 168 deletions

View File

@ -4,7 +4,7 @@ android {
compileSdkVersion 27 compileSdkVersion 27
defaultConfig { defaultConfig {
applicationId "graphics.pathfinder.pathfinderdemo" applicationId "graphics.pathfinder.pathfinderdemo"
minSdkVersion 21 minSdkVersion 24
targetSdkVersion 27 targetSdkVersion 27
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@ -22,6 +22,7 @@ android {
} }
dependencies { dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
compile 'com.google.vr:sdk-base:1.160.0' compile 'com.google.vr:sdk-base:1.160.0'
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])

View File

@ -1,26 +0,0 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("graphics.pathfinder.pathfinderdemo", appContext.getPackageName());
}
}

View File

@ -2,6 +2,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="graphics.pathfinder.pathfinderdemo"> package="graphics.pathfinder.pathfinderdemo">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.software.vr.mode"
android:required="false" />
<uses-feature
android:name="android.hardware.vr.high_performance"
android:required="false" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -10,41 +19,49 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".PathfinderActivity" android:name=".PathfinderDemoActivity"
android:configChanges="density|navigation|orientation|keyboardHidden|screenSize|uiMode" android:configChanges="density|navigation|orientation|keyboardHidden|screenSize|uiMode"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/VrActivityTheme" android:resizeableActivity="false"
android:resizeableActivity="false"> android:theme="@style/AppTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<!-- This marks the Activity as a Daydream Activity and allows it <!--
to be launched from the Daydream Home. --> This marks the Activity as a Daydream Activity and allows it
to be launched from the Daydream Home.
-->
<category android:name="com.google.intent.category.DAYDREAM" /> <category android:name="com.google.intent.category.DAYDREAM" />
<!-- This marks the Activity as a Cardboard Activity and allows it <!--
to be launched from the Cardboard app. --> This marks the Activity as a Cardboard Activity and allows it
to be launched from the Cardboard app.
-->
<category android:name="com.google.intent.category.CARDBOARD" /> <category android:name="com.google.intent.category.CARDBOARD" />
<!-- This allows this Activity to be launched from the traditional <!--
This allows this Activity to be launched from the traditional
Android 2D launcher as well. Remove it if you do not want Android 2D launcher as well. Remove it if you do not want
this Activity to be launched directly from the 2D launcher. --> this Activity to be launched directly from the 2D launcher.
-->
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".PathfinderDemoVRListenerService"
<service
android:name=".PathfinderDemoVRListenerService"
android:label="@string/service_name" android:label="@string/service_name"
android:permission="android.permission.BIND_VR_LISTENER_SERVICE"> android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
<intent-filter> <intent-filter>
<action android:name="android.service.vr.VrListenerService" /> <action android:name="android.service.vr.VrListenerService" />
</intent-filter> </intent-filter>
</service> </service>
<activity
android:name=".PathfinderDemoFileBrowserActivity"
android:label="@string/title_activity_pathfinder_demo_file_browser"></activity>
</application> </application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.software.vr.mode" android:required="false"/>
<uses-feature android:name="android.hardware.vr.high_performance" android:required="false"/>
</manifest> </manifest>

View File

@ -0,0 +1 @@
../../../../../../resources

View File

@ -18,17 +18,14 @@ import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import com.google.vr.cardboard.AndroidNCompat;
/** /**
* An example full-screen activity that shows and hides the system UI (i.e. * An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction. * status bar and navigation/system bar) with user interaction.
*/ */
public class PathfinderActivity extends Activity { public class PathfinderDemoActivity extends Activity {
private PathfinderDemoRenderer mRenderer; private PathfinderDemoRenderer mRenderer;
/** /**
@ -83,32 +80,20 @@ public class PathfinderActivity extends Activity {
mContentView.setStereoModeEnabled(false); mContentView.setStereoModeEnabled(false);
setVRMode(false); setVRMode(false);
/*
// Set up the user interaction to manually show or hide the system UI.
mContentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
toggle();
}
});
*/
mContentView.setEGLContextClientVersion(3); mContentView.setEGLContextClientVersion(3);
mRenderer = new PathfinderDemoRenderer(this); mRenderer = new PathfinderDemoRenderer(this);
mContentView.setRenderer(mRenderer); mContentView.setRenderer(mRenderer);
mContentView.setOnTouchListener(new View.OnTouchListener() { mContentView.setOnTouchListener(new View.OnTouchListener() {
@Override @Override
public boolean onTouch(View view, MotionEvent event) { public boolean onTouch(final View view, final MotionEvent event) {
int x = Math.round(event.getX()); final int x = Math.round(event.getX());
int y = Math.round(event.getY()); final int y = Math.round(event.getY());
switch (event.getActionMasked()) { switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
Log.i("Pathfinder", "DOWN " + x + " " + y);
PathfinderDemoRenderer.pushMouseDownEvent(x, y); PathfinderDemoRenderer.pushMouseDownEvent(x, y);
break; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
Log.i("Pathfinder", "MOVE " + x + " " + y);
PathfinderDemoRenderer.pushMouseDraggedEvent(x, y); PathfinderDemoRenderer.pushMouseDraggedEvent(x, y);
break; break;
} }
@ -127,13 +112,13 @@ public class PathfinderActivity extends Activity {
@Override @Override
public void onSensorChanged(SensorEvent event) { public void onSensorChanged(SensorEvent event) {
// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_Angles_Conversion // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_Angles_Conversion
float[] q = event.values; final float[] q = event.values;
float pitch = (float)Math.asin(2.0 * (q[0] * q[2] - q[3] * q[1])); final float pitch = (float)Math.asin(2.0 * (q[0] * q[2] - q[3] * q[1]));
float yaw = (float)Math.atan2(2.0 * (q[0] * q[3] + q[1] * q[2]), final float yaw = (float)Math.atan2(2.0 * (q[0] * q[3] + q[1] * q[2]),
1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3])); 1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3]));
float deltaPitch = pitch - mPitch; final float deltaPitch = pitch - mPitch;
float deltaYaw = yaw - mYaw; final float deltaYaw = yaw - mYaw;
mPitch = pitch; mPitch = pitch;
mYaw = yaw; mYaw = yaw;
@ -157,4 +142,9 @@ public class PathfinderActivity extends Activity {
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
} }
public void presentOpenSVGDialog() {
final Intent intent = new Intent(this, PathfinderDemoFileBrowserActivity.class);
startActivity(intent);
}
} }

View File

@ -0,0 +1,48 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.io.IOException;
public class PathfinderDemoFileBrowserActivity extends Activity {
private ListView mBrowserView;
private static String SVG_RESOURCE_PATH = "svg/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pathfinder_demo_file_browser);
mBrowserView = findViewById(R.id.fileBrowserBrowser);
try {
final AssetManager assetManager = getAssets();
final String[] svgFilenames = assetManager.list("resources/" + SVG_RESOURCE_PATH);
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this,
R.layout.layout_pathfinder_demo_file_browser_list_item,
svgFilenames);
mBrowserView.setAdapter(adapter);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
mBrowserView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textView = (TextView)view;
PathfinderDemoRenderer.pushOpenSVGEvent(SVG_RESOURCE_PATH + textView.getText());
finish();
}
});
}
}

View File

@ -1,8 +1,7 @@
package graphics.pathfinder.pathfinderdemo; package graphics.pathfinder.pathfinderdemo;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.google.vr.sdk.base.Eye; import com.google.vr.sdk.base.Eye;
import com.google.vr.sdk.base.GvrView; import com.google.vr.sdk.base.GvrView;
@ -11,11 +10,12 @@ import com.google.vr.sdk.base.Viewport;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLConfig;
public class PathfinderDemoRenderer extends Object implements GvrView.Renderer { public class PathfinderDemoRenderer extends Object implements GvrView.Renderer {
private PathfinderActivity mActivity; private final PathfinderDemoActivity mActivity;
private boolean mInitialized; private boolean mInitialized;
private boolean mInVRMode; private boolean mInVRMode;
private static native void init(PathfinderDemoResourceLoader resourceLoader, private static native void init(PathfinderDemoActivity activity,
PathfinderDemoResourceLoader resourceLoader,
int width, int width,
int height); int height);
@ -33,20 +33,21 @@ public class PathfinderDemoRenderer extends Object implements GvrView.Renderer {
public static native void pushLookEvent(float pitch, float yaw); public static native void pushLookEvent(float pitch, float yaw);
public static native void pushOpenSVGEvent(String path);
static { static {
System.loadLibrary("pathfinder_android_demo"); System.loadLibrary("pathfinder_android_demo");
} }
PathfinderDemoRenderer(PathfinderActivity activity) { PathfinderDemoRenderer(PathfinderDemoActivity activity) {
super(); super();
mActivity = activity; mActivity = activity;
mInitialized = false; mInitialized = false;
} }
@RequiresApi(api = Build.VERSION_CODES.N)
@Override @Override
public void onDrawFrame(HeadTransform headTransform, Eye leftEye, Eye rightEye) { public void onDrawFrame(HeadTransform headTransform, Eye leftEye, Eye rightEye) {
boolean inVR = prepareFrame() > 1; final boolean inVR = prepareFrame() > 1;
if (inVR != mInVRMode) { if (inVR != mInVRMode) {
mInVRMode = inVR; mInVRMode = inVR;
try { try {
@ -69,7 +70,10 @@ public class PathfinderDemoRenderer extends Object implements GvrView.Renderer {
@Override @Override
public void onSurfaceChanged(int width, int height) { public void onSurfaceChanged(int width, int height) {
if (!mInitialized) { if (!mInitialized) {
init(new PathfinderDemoResourceLoader(mActivity.getAssets()), width, height); init(mActivity,
new PathfinderDemoResourceLoader(mActivity.getAssets()),
width,
height);
mInitialized = true; mInitialized = true;
} else { } else {
pushWindowResizedEvent(width, height); pushWindowResizedEvent(width, height);

View File

@ -0,0 +1,40 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class PathfinderDemoResourceLoader {
private AssetManager m_assetManager;
PathfinderDemoResourceLoader(AssetManager assetManager) {
m_assetManager = assetManager;
}
ByteBuffer slurp(String path) {
try {
InputStream inputStream = m_assetManager.open("resources/" + path);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
while (true) {
int nRead = inputStream.read(buffer, 0, buffer.length);
if (nRead == -1)
break;
outputStream.write(buffer, 0, nRead);
}
byte[] outputBytes = outputStream.toByteArray();
ByteBuffer resultBuffer = ByteBuffer.allocateDirect(outputStream.size());
resultBuffer.put(outputBytes);
return resultBuffer;
} catch (IOException exception) {
Log.e("Pathfinder", "Resource not found: " + path);
return null;
}
}
}

View File

@ -1,10 +1,7 @@
package graphics.pathfinder.pathfinderdemo; package graphics.pathfinder.pathfinderdemo;
import android.content.Context; import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.google.vr.sdk.base.GvrView; import com.google.vr.sdk.base.GvrView;
public class PathfinderDemoSurfaceView extends GvrView { public class PathfinderDemoSurfaceView extends GvrView {

View File

@ -0,0 +1,10 @@
package graphics.pathfinder.pathfinderdemo;
import android.os.Build;
import android.service.vr.VrListenerService;
import android.support.annotation.RequiresApi;
@RequiresApi(api = Build.VERSION_CODES.N)
public class PathfinderDemoVRListenerService extends VrListenerService {
}

View File

@ -0,0 +1 @@
../../../../../../../target/aarch64-linux-android/release/libpathfinder_android_demo.so

View File

@ -4,7 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#0099cc" android:background="#0099cc"
tools:context=".PathfinderActivity"> tools:context=".PathfinderDemoActivity">
<!-- The primary full-screen view. This can be replaced with whatever view <!-- The primary full-screen view. This can be replaced with whatever view
is needed to present your content, e.g. VideoView, SurfaceView, is needed to present your content, e.g. VideoView, SurfaceView,

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".PathfinderDemoFileBrowserActivity">
<ListView
android:id="@+id/fileBrowserBrowser"
style="@android:style/Widget.Material.ListView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:lineSpacingExtra="14sp"
android:textAppearance="@android:style/TextAppearance"
android:textSize="18sp">
</TextView>

View File

@ -4,4 +4,6 @@
<string name="dummy_button">Dummy Button</string> <string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string> <string name="dummy_content">DUMMY\nCONTENT</string>
<string name="service_name">PathfinderVRListenerService</string> <string name="service_name">PathfinderVRListenerService</string>
<string name="title_activity_pathfinder_demo_file_browser">PathfinderDemoFileBrowserActivity
</string>
</resources> </resources>

View File

@ -1,17 +0,0 @@
package graphics.pathfinder.pathfinderdemo;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -12,9 +12,9 @@
extern crate lazy_static; extern crate lazy_static;
use jni::{JNIEnv, JavaVM}; use jni::{JNIEnv, JavaVM};
use jni::objects::{GlobalRef, JByteBuffer, JClass, JObject, JValue}; use jni::objects::{GlobalRef, JByteBuffer, JClass, JObject, JString, JValue};
use pathfinder_demo::DemoApp; use pathfinder_demo::DemoApp;
use pathfinder_demo::window::{Event, Window, WindowSize}; use pathfinder_demo::window::{Event, SVGPath, Window, WindowSize};
use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_gl::GLVersion; use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::resources::ResourceLoader;
@ -31,6 +31,7 @@ lazy_static! {
thread_local! { thread_local! {
static DEMO_APP: RefCell<Option<DemoApp<WindowImpl>>> = RefCell::new(None); static DEMO_APP: RefCell<Option<DemoApp<WindowImpl>>> = RefCell::new(None);
static JAVA_ACTIVITY: RefCell<Option<JavaActivity>> = RefCell::new(None);
static JAVA_RESOURCE_LOADER: RefCell<Option<JavaResourceLoader>> = RefCell::new(None); static JAVA_RESOURCE_LOADER: RefCell<Option<JavaResourceLoader>> = RefCell::new(None);
} }
@ -40,12 +41,16 @@ static RESOURCE_LOADER: AndroidResourceLoader = AndroidResourceLoader;
pub unsafe extern "system" fn pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_init(env: JNIEnv, Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_init(env: JNIEnv,
class: JClass, class: JClass,
activity: JObject,
loader: JObject, loader: JObject,
width: i32, width: i32,
height: i32) { height: i32) {
let logical_size = Point2DI32::new(width, height); let logical_size = Point2DI32::new(width, height);
let window_size = WindowSize { logical_size, backing_scale_factor: 1.0 }; let window_size = WindowSize { logical_size, backing_scale_factor: 1.0 };
JAVA_ACTIVITY.with(|java_activity| {
*java_activity.borrow_mut() = Some(JavaActivity::new(env.clone(), activity));
});
JAVA_RESOURCE_LOADER.with(|java_resource_loader| { JAVA_RESOURCE_LOADER.with(|java_resource_loader| {
*java_resource_loader.borrow_mut() = Some(JavaResourceLoader::new(env, loader)); *java_resource_loader.borrow_mut() = Some(JavaResourceLoader::new(env, loader));
}); });
@ -139,6 +144,16 @@ pub unsafe extern "system" fn
EVENT_QUEUE.lock().unwrap().push(Event::Look { pitch, yaw }) EVENT_QUEUE.lock().unwrap().push(Event::Look { pitch, yaw })
} }
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushOpenSVGEvent(
env: JNIEnv,
_: JClass,
string: JObject) {
let string: String = env.get_string(JString::from(string)).unwrap().into();
EVENT_QUEUE.lock().unwrap().push(Event::OpenSVG(SVGPath::Resource(string)))
}
struct WindowImpl; struct WindowImpl;
impl Window for WindowImpl { impl Window for WindowImpl {
@ -163,9 +178,16 @@ impl Window for WindowImpl {
fn push_user_event(message_type: u32, message_data: u32) { fn push_user_event(message_type: u32, message_data: u32) {
} }
fn run_open_dialog(&self, extension: &str) -> Result<PathBuf, ()> { fn present_open_svg_dialog(&mut self) {
// TODO(pcwalton) JAVA_ACTIVITY.with(|java_activity| {
Err(()) let mut java_activity = java_activity.borrow_mut();
let java_activity = java_activity.as_mut().unwrap();
let env = java_activity.vm.get_env().unwrap();
env.call_method(java_activity.activity.as_obj(),
"presentOpenSVGDialog",
"()V",
&[]).unwrap();
});
} }
fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()> { fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()> {
@ -197,6 +219,20 @@ impl ResourceLoader for AndroidResourceLoader {
} }
} }
struct JavaActivity {
activity: GlobalRef,
vm: JavaVM,
}
impl JavaActivity {
fn new(env: JNIEnv, activity: JObject) -> JavaActivity {
JavaActivity {
activity: env.new_global_ref(activity).unwrap(),
vm: env.get_java_vm().unwrap(),
}
}
}
struct JavaResourceLoader { struct JavaResourceLoader {
loader: GlobalRef, loader: GlobalRef,
vm: JavaVM, vm: JavaVM,

View File

@ -12,7 +12,7 @@
use crate::device::{GroundLineVertexArray, GroundProgram, GroundSolidVertexArray}; use crate::device::{GroundLineVertexArray, GroundProgram, GroundSolidVertexArray};
use crate::ui::{DemoUI, UIAction}; use crate::ui::{DemoUI, UIAction};
use crate::window::{Event, Keycode, Window, WindowSize}; use crate::window::{Event, Keycode, SVGPath, Window, WindowSize};
use clap::{App, Arg}; use clap::{App, Arg};
use image::ColorType; use image::ColorType;
use jemallocator; use jemallocator;
@ -328,6 +328,23 @@ impl<W> DemoApp<W> where W: Window {
self.dirty = true; self.dirty = true;
} }
} }
Event::OpenSVG(ref svg_path) => {
let built_svg = load_scene(self.window.resource_loader(), svg_path);
self.ui.message = get_svg_building_message(&built_svg);
let view_box_size = view_box_size(self.ui.mode, &self.window_size);
self.scene_view_box = built_svg.scene.view_box;
self.scene_is_monochrome = built_svg.scene.is_monochrome();
self.camera = if self.ui.mode == Mode::TwoD {
Camera::new_2d(self.scene_view_box, view_box_size)
} else {
Camera::new_3d(self.scene_view_box)
};
self.scene_thread_proxy.load_scene(built_svg.scene, view_box_size);
self.dirty = true;
}
Event::User { message_type: event_id, message_data: expected_epoch } if Event::User { message_type: event_id, message_data: expected_epoch } if
event_id == self.expire_message_event_id && event_id == self.expire_message_event_id &&
expected_epoch as u32 == self.message_epoch => { expected_epoch as u32 == self.message_epoch => {
@ -395,7 +412,7 @@ impl<W> DemoApp<W> where W: Window {
let mut ui_action = UIAction::None; let mut ui_action = UIAction::None;
self.ui.update(&self.renderer.device, self.ui.update(&self.renderer.device,
&self.window, &mut self.window,
&mut self.renderer.debug_ui, &mut self.renderer.debug_ui,
&mut ui_action); &mut ui_action);
@ -539,26 +556,6 @@ impl<W> DemoApp<W> where W: Window {
match ui_action { match ui_action {
UIAction::None => {} UIAction::None => {}
UIAction::OpenFile(ref path) => {
let built_svg = load_scene(self.window.resource_loader(), &Some((*path).clone()));
self.ui.message = get_svg_building_message(&built_svg);
self.scene_view_box = built_svg.scene.view_box;
self.scene_is_monochrome = built_svg.scene.is_monochrome();
let drawable_size = self.window_size.device_size();
self.scene_thread_proxy.set_drawable_size(drawable_size);
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);
self.dirty = true;
}
UIAction::TakeScreenshot(ref path) => { UIAction::TakeScreenshot(ref path) => {
self.pending_screenshot_path = Some((*path).clone()); self.pending_screenshot_path = Some((*path).clone());
self.dirty = true; self.dirty = true;
@ -626,8 +623,8 @@ impl SceneThreadProxy {
SceneThreadProxy { sender: main_to_scene_sender, receiver: scene_to_main_receiver } SceneThreadProxy { sender: main_to_scene_sender, receiver: scene_to_main_receiver }
} }
fn load_scene(&self, scene: Scene) { fn load_scene(&self, scene: Scene, view_box_size: Point2DI32) {
self.sender.send(MainToSceneMsg::LoadScene(scene)).unwrap(); self.sender.send(MainToSceneMsg::LoadScene { scene, view_box_size }).unwrap();
} }
fn set_drawable_size(&self, drawable_size: Point2DI32) { fn set_drawable_size(&self, drawable_size: Point2DI32) {
@ -653,7 +650,11 @@ impl SceneThread {
fn run(mut self) { fn run(mut self) {
while let Ok(msg) = self.receiver.recv() { while let Ok(msg) = self.receiver.recv() {
match msg { match msg {
MainToSceneMsg::LoadScene(scene) => self.scene = scene, MainToSceneMsg::LoadScene { scene, view_box_size } => {
self.scene = scene;
self.scene.view_box = RectF32::new(Point2DF32::default(),
view_box_size.to_f32());
}
MainToSceneMsg::SetDrawableSize(size) => { MainToSceneMsg::SetDrawableSize(size) => {
self.scene.view_box = RectF32::new(Point2DF32::default(), size.to_f32()); self.scene.view_box = RectF32::new(Point2DF32::default(), size.to_f32());
} }
@ -677,7 +678,7 @@ impl SceneThread {
} }
enum MainToSceneMsg { enum MainToSceneMsg {
LoadScene(Scene), LoadScene { scene: Scene, view_box_size: Point2DI32 },
SetDrawableSize(Point2DI32), SetDrawableSize(Point2DI32),
Build(BuildOptions), Build(BuildOptions),
} }
@ -701,7 +702,7 @@ pub struct RenderScene {
pub struct Options { pub struct Options {
jobs: Option<usize>, jobs: Option<usize>,
mode: Mode, mode: Mode,
input_path: Option<PathBuf>, input_path: SVGPath,
} }
impl Options { impl Options {
@ -732,7 +733,10 @@ impl Options {
Mode::TwoD Mode::TwoD
}; };
let input_path = matches.value_of("INPUT").map(PathBuf::from); let input_path = match matches.value_of("INPUT") {
None => SVGPath::Default,
Some(path) => SVGPath::Path(PathBuf::from(path)),
};
// Set up Rayon. // Set up Rayon.
let mut thread_pool_builder = ThreadPoolBuilder::new(); let mut thread_pool_builder = ThreadPoolBuilder::new();
@ -764,19 +768,16 @@ struct RenderStats {
stats: Stats, stats: Stats,
} }
fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &Option<PathBuf>) -> BuiltSVG { fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath) -> BuiltSVG {
let mut data; let mut data;
match *input_path { match *input_path {
Some(ref input_path) => { SVGPath::Default => data = resource_loader.slurp(DEFAULT_SVG_VIRTUAL_PATH).unwrap(),
SVGPath::Resource(ref name) => data = resource_loader.slurp(name).unwrap(),
SVGPath::Path(ref path) => {
data = vec![]; data = vec![];
let mut file = match File::open(input_path) { File::open(path).unwrap().read_to_end(&mut data).unwrap();
Ok(file) => file, }
Err(_) => panic!(),
}; };
file.read_to_end(&mut data).unwrap();
}
None => data = resource_loader.slurp(DEFAULT_SVG_VIRTUAL_PATH).unwrap(),
}
BuiltSVG::from_tree(Tree::from_data(&data, &UsvgOptions::default()).unwrap()) BuiltSVG::from_tree(Tree::from_data(&data, &UsvgOptions::default()).unwrap())
} }

View File

@ -107,7 +107,7 @@ impl<D> DemoUI<D> where D: Device {
pub fn update<W>(&mut self, pub fn update<W>(&mut self,
device: &D, device: &D,
window: &W, window: &mut W,
debug_ui: &mut DebugUI<D>, debug_ui: &mut DebugUI<D>,
action: &mut UIAction) action: &mut UIAction)
where W: Window { where W: Window {
@ -139,9 +139,7 @@ impl<D> DemoUI<D> where D: Device {
if debug_ui.ui.draw_button(device, position, &self.open_texture) { if debug_ui.ui.draw_button(device, position, &self.open_texture) {
// FIXME(pcwalton): This is not sufficient for Android, where we will need to take in // FIXME(pcwalton): This is not sufficient for Android, where we will need to take in
// the contents of the file. // the contents of the file.
if let Ok(file) = window.run_open_dialog("svg") { window.present_open_svg_dialog();
*action = UIAction::OpenFile(file);
}
} }
debug_ui.ui.draw_tooltip(device, "Open SVG", RectI32::new(position, button_size)); debug_ui.ui.draw_tooltip(device, "Open SVG", RectI32::new(position, button_size));
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0); position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
@ -337,7 +335,6 @@ impl<D> DemoUI<D> where D: Device {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum UIAction { pub enum UIAction {
None, None,
OpenFile(PathBuf),
TakeScreenshot(PathBuf), TakeScreenshot(PathBuf),
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,

View File

@ -13,7 +13,6 @@
use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_gl::GLVersion; use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::resources::ResourceLoader;
use std::io::Error;
use std::path::PathBuf; use std::path::PathBuf;
pub trait Window { pub trait Window {
@ -23,7 +22,7 @@ pub trait Window {
fn resource_loader(&self) -> &dyn ResourceLoader; fn resource_loader(&self) -> &dyn ResourceLoader;
fn create_user_event_id(&self) -> u32; fn create_user_event_id(&self) -> u32;
fn push_user_event(message_type: u32, message_data: u32); fn push_user_event(message_type: u32, message_data: u32);
fn run_open_dialog(&self, extension: &str) -> Result<PathBuf, ()>; fn present_open_svg_dialog(&mut self);
fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()>; fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()>;
} }
@ -37,6 +36,7 @@ pub enum Event {
MouseDragged(Point2DI32), MouseDragged(Point2DI32),
Zoom(f32), Zoom(f32),
Look { pitch: f32, yaw: f32 }, Look { pitch: f32, yaw: f32 },
OpenSVG(SVGPath),
User { message_type: u32, message_data: u32 }, User { message_type: u32, message_data: u32 },
} }
@ -58,3 +58,10 @@ impl WindowSize {
self.logical_size.to_f32().scale(self.backing_scale_factor).to_i32() self.logical_size.to_f32().scale(self.backing_scale_factor).to_i32()
} }
} }
#[derive(Clone)]
pub enum SVGPath {
Default,
Resource(String),
Path(PathBuf),
}

View File

@ -12,7 +12,7 @@
use nfd::Response; use nfd::Response;
use pathfinder_demo::DemoApp; use pathfinder_demo::DemoApp;
use pathfinder_demo::window::{Event, Keycode, Window, WindowSize}; use pathfinder_demo::window::{Event, Keycode, SVGPath, Window, WindowSize};
use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_gl::GLVersion; use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::{FilesystemResourceLoader, ResourceLoader}; use pathfinder_gpu::resources::{FilesystemResourceLoader, ResourceLoader};
@ -58,6 +58,8 @@ struct WindowImpl {
#[allow(dead_code)] #[allow(dead_code)]
gl_context: GLContext, gl_context: GLContext,
resource_loader: FilesystemResourceLoader, resource_loader: FilesystemResourceLoader,
selected_file: Option<PathBuf>,
open_svg_message_type: u32,
} }
impl Window for WindowImpl { impl Window for WindowImpl {
@ -96,10 +98,10 @@ impl Window for WindowImpl {
} }
} }
fn run_open_dialog(&self, extension: &str) -> Result<PathBuf, ()> { fn present_open_svg_dialog(&mut self) {
match nfd::open_file_dialog(Some(extension), None) { if let Ok(Response::Okay(path)) = nfd::open_file_dialog(Some("svg"), None) {
Ok(Response::Okay(file)) => Ok(PathBuf::from(file)), self.selected_file = Some(PathBuf::from(path));
_ => Err(()), WindowImpl::push_user_event(self.open_svg_message_type, 0);
} }
} }
@ -114,6 +116,7 @@ impl Window for WindowImpl {
impl WindowImpl { impl WindowImpl {
fn new() -> WindowImpl { fn new() -> WindowImpl {
SDL_VIDEO.with(|sdl_video| { SDL_VIDEO.with(|sdl_video| {
SDL_EVENT.with(|sdl_event| {
let (window, gl_context, event_pump); let (window, gl_context, event_pump);
let gl_attributes = sdl_video.gl_attr(); let gl_attributes = sdl_video.gl_attr();
@ -138,7 +141,19 @@ impl WindowImpl {
let resource_loader = FilesystemResourceLoader::locate(); let resource_loader = FilesystemResourceLoader::locate();
WindowImpl { window, event_pump, gl_context, resource_loader } let open_svg_message_type = unsafe {
sdl_event.register_event().unwrap()
};
WindowImpl {
window,
event_pump,
gl_context,
resource_loader,
open_svg_message_type,
selected_file: None,
}
})
}) })
} }
@ -171,6 +186,9 @@ impl WindowImpl {
fn convert_sdl_event(&self, sdl_event: SDLEvent) -> Option<Event> { fn convert_sdl_event(&self, sdl_event: SDLEvent) -> Option<Event> {
match sdl_event { match sdl_event {
SDLEvent::User { type_, .. } if type_ == self.open_svg_message_type => {
Some(Event::OpenSVG(SVGPath::Path(self.selected_file.clone().unwrap())))
}
SDLEvent::User { type_, code, .. } => { SDLEvent::User { type_, code, .. } => {
Some(Event::User { message_type: type_, message_data: code as u32 }) Some(Event::User { message_type: type_, message_data: code as u32 })
} }