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
defaultConfig {
applicationId "graphics.pathfinder.pathfinderdemo"
minSdkVersion 21
minSdkVersion 24
targetSdkVersion 27
versionCode 1
versionName "1.0"
@ -22,6 +22,7 @@ android {
}
dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
compile 'com.google.vr:sdk-base:1.160.0'
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"
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
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@ -10,41 +19,49 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".PathfinderActivity"
android:name=".PathfinderDemoActivity"
android:configChanges="density|navigation|orientation|keyboardHidden|screenSize|uiMode"
android:label="@string/app_name"
android:theme="@style/VrActivityTheme"
android:resizeableActivity="false">
android:resizeableActivity="false"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<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" />
<!-- 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" />
<!-- 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
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" />
</intent-filter>
</activity>
<service android:name=".PathfinderDemoVRListenerService"
<service
android:name=".PathfinderDemoVRListenerService"
android:label="@string/service_name"
android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.vr.VrListenerService" />
</intent-filter>
</service>
<activity
android:name=".PathfinderDemoFileBrowserActivity"
android:label="@string/title_activity_pathfinder_demo_file_browser"></activity>
</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>

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.content.ContextCompat;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.google.vr.cardboard.AndroidNCompat;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class PathfinderActivity extends Activity {
public class PathfinderDemoActivity extends Activity {
private PathfinderDemoRenderer mRenderer;
/**
@ -83,32 +80,20 @@ public class PathfinderActivity extends Activity {
mContentView.setStereoModeEnabled(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);
mRenderer = new PathfinderDemoRenderer(this);
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());
public boolean onTouch(final View view, final MotionEvent event) {
final int x = Math.round(event.getX());
final 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;
}
@ -127,13 +112,13 @@ public class PathfinderActivity extends Activity {
@Override
public void onSensorChanged(SensorEvent event) {
// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_Angles_Conversion
float[] q = event.values;
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]),
1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3]));
final float[] q = event.values;
final float pitch = (float)Math.asin(2.0 * (q[0] * q[2] - q[3] * q[1]));
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]));
float deltaPitch = pitch - mPitch;
float deltaYaw = yaw - mYaw;
final float deltaPitch = pitch - mPitch;
final float deltaYaw = yaw - mYaw;
mPitch = pitch;
mYaw = yaw;
@ -157,4 +142,9 @@ public class PathfinderActivity extends Activity {
protected void onPostCreate(Bundle 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;
import android.content.Intent;
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.GvrView;
@ -11,11 +10,12 @@ import com.google.vr.sdk.base.Viewport;
import javax.microedition.khronos.egl.EGLConfig;
public class PathfinderDemoRenderer extends Object implements GvrView.Renderer {
private PathfinderActivity mActivity;
private final PathfinderDemoActivity mActivity;
private boolean mInitialized;
private boolean mInVRMode;
private static native void init(PathfinderDemoResourceLoader resourceLoader,
private static native void init(PathfinderDemoActivity activity,
PathfinderDemoResourceLoader resourceLoader,
int width,
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 pushOpenSVGEvent(String path);
static {
System.loadLibrary("pathfinder_android_demo");
}
PathfinderDemoRenderer(PathfinderActivity activity) {
PathfinderDemoRenderer(PathfinderDemoActivity activity) {
super();
mActivity = activity;
mInitialized = false;
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onDrawFrame(HeadTransform headTransform, Eye leftEye, Eye rightEye) {
boolean inVR = prepareFrame() > 1;
final boolean inVR = prepareFrame() > 1;
if (inVR != mInVRMode) {
mInVRMode = inVR;
try {
@ -69,7 +70,10 @@ public class PathfinderDemoRenderer extends Object implements GvrView.Renderer {
@Override
public void onSurfaceChanged(int width, int height) {
if (!mInitialized) {
init(new PathfinderDemoResourceLoader(mActivity.getAssets()), width, height);
init(mActivity,
new PathfinderDemoResourceLoader(mActivity.getAssets()),
width,
height);
mInitialized = true;
} else {
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;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.google.vr.sdk.base.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_height="match_parent"
android:background="#0099cc"
tools:context=".PathfinderActivity">
tools:context=".PathfinderDemoActivity">
<!-- The primary full-screen view. This can be replaced with whatever view
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_content">DUMMY\nCONTENT</string>
<string name="service_name">PathfinderVRListenerService</string>
<string name="title_activity_pathfinder_demo_file_browser">PathfinderDemoFileBrowserActivity
</string>
</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;
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::window::{Event, Window, WindowSize};
use pathfinder_demo::window::{Event, SVGPath, Window, WindowSize};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader;
@ -31,6 +31,7 @@ lazy_static! {
thread_local! {
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);
}
@ -40,12 +41,16 @@ static RESOURCE_LOADER: AndroidResourceLoader = AndroidResourceLoader;
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_init(env: JNIEnv,
class: JClass,
activity: JObject,
loader: JObject,
width: i32,
height: i32) {
let logical_size = Point2DI32::new(width, height);
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.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 })
}
#[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;
impl Window for WindowImpl {
@ -163,9 +178,16 @@ impl Window for WindowImpl {
fn push_user_event(message_type: u32, message_data: u32) {
}
fn run_open_dialog(&self, extension: &str) -> Result<PathBuf, ()> {
// TODO(pcwalton)
Err(())
fn present_open_svg_dialog(&mut self) {
JAVA_ACTIVITY.with(|java_activity| {
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, ()> {
@ -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 {
loader: GlobalRef,
vm: JavaVM,

View File

@ -12,7 +12,7 @@
use crate::device::{GroundLineVertexArray, GroundProgram, GroundSolidVertexArray};
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 image::ColorType;
use jemallocator;
@ -328,6 +328,23 @@ impl<W> DemoApp<W> where W: Window {
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_id == self.expire_message_event_id &&
expected_epoch as u32 == self.message_epoch => {
@ -395,7 +412,7 @@ impl<W> DemoApp<W> where W: Window {
let mut ui_action = UIAction::None;
self.ui.update(&self.renderer.device,
&self.window,
&mut self.window,
&mut self.renderer.debug_ui,
&mut ui_action);
@ -539,26 +556,6 @@ impl<W> DemoApp<W> where W: Window {
match ui_action {
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) => {
self.pending_screenshot_path = Some((*path).clone());
self.dirty = true;
@ -626,8 +623,8 @@ impl SceneThreadProxy {
SceneThreadProxy { sender: main_to_scene_sender, receiver: scene_to_main_receiver }
}
fn load_scene(&self, scene: Scene) {
self.sender.send(MainToSceneMsg::LoadScene(scene)).unwrap();
fn load_scene(&self, scene: Scene, view_box_size: Point2DI32) {
self.sender.send(MainToSceneMsg::LoadScene { scene, view_box_size }).unwrap();
}
fn set_drawable_size(&self, drawable_size: Point2DI32) {
@ -653,7 +650,11 @@ impl SceneThread {
fn run(mut self) {
while let Ok(msg) = self.receiver.recv() {
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) => {
self.scene.view_box = RectF32::new(Point2DF32::default(), size.to_f32());
}
@ -677,7 +678,7 @@ impl SceneThread {
}
enum MainToSceneMsg {
LoadScene(Scene),
LoadScene { scene: Scene, view_box_size: Point2DI32 },
SetDrawableSize(Point2DI32),
Build(BuildOptions),
}
@ -701,7 +702,7 @@ pub struct RenderScene {
pub struct Options {
jobs: Option<usize>,
mode: Mode,
input_path: Option<PathBuf>,
input_path: SVGPath,
}
impl Options {
@ -732,7 +733,10 @@ impl Options {
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.
let mut thread_pool_builder = ThreadPoolBuilder::new();
@ -764,19 +768,16 @@ struct RenderStats {
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;
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![];
let mut file = match File::open(input_path) {
Ok(file) => file,
Err(_) => panic!(),
};
file.read_to_end(&mut data).unwrap();
File::open(path).unwrap().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())
}

View File

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

View File

@ -13,7 +13,6 @@
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader;
use std::io::Error;
use std::path::PathBuf;
pub trait Window {
@ -23,7 +22,7 @@ pub trait Window {
fn resource_loader(&self) -> &dyn ResourceLoader;
fn create_user_event_id(&self) -> 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, ()>;
}
@ -37,6 +36,7 @@ pub enum Event {
MouseDragged(Point2DI32),
Zoom(f32),
Look { pitch: f32, yaw: f32 },
OpenSVG(SVGPath),
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()
}
}
#[derive(Clone)]
pub enum SVGPath {
Default,
Resource(String),
Path(PathBuf),
}

View File

@ -12,7 +12,7 @@
use nfd::Response;
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_gl::GLVersion;
use pathfinder_gpu::resources::{FilesystemResourceLoader, ResourceLoader};
@ -58,6 +58,8 @@ struct WindowImpl {
#[allow(dead_code)]
gl_context: GLContext,
resource_loader: FilesystemResourceLoader,
selected_file: Option<PathBuf>,
open_svg_message_type: u32,
}
impl Window for WindowImpl {
@ -96,10 +98,10 @@ impl Window for WindowImpl {
}
}
fn run_open_dialog(&self, extension: &str) -> Result<PathBuf, ()> {
match nfd::open_file_dialog(Some(extension), None) {
Ok(Response::Okay(file)) => Ok(PathBuf::from(file)),
_ => Err(()),
fn present_open_svg_dialog(&mut self) {
if let Ok(Response::Okay(path)) = nfd::open_file_dialog(Some("svg"), None) {
self.selected_file = Some(PathBuf::from(path));
WindowImpl::push_user_event(self.open_svg_message_type, 0);
}
}
@ -114,31 +116,44 @@ impl Window for WindowImpl {
impl WindowImpl {
fn new() -> WindowImpl {
SDL_VIDEO.with(|sdl_video| {
let (window, gl_context, event_pump);
SDL_EVENT.with(|sdl_event| {
let (window, gl_context, event_pump);
let gl_attributes = sdl_video.gl_attr();
gl_attributes.set_context_profile(GLProfile::Core);
gl_attributes.set_context_version(3, 3);
gl_attributes.set_depth_size(24);
gl_attributes.set_stencil_size(8);
let gl_attributes = sdl_video.gl_attr();
gl_attributes.set_context_profile(GLProfile::Core);
gl_attributes.set_context_version(3, 3);
gl_attributes.set_depth_size(24);
gl_attributes.set_stencil_size(8);
window = sdl_video.window("Pathfinder Demo",
DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT)
.opengl()
.resizable()
.allow_highdpi()
.build()
.unwrap();
window = sdl_video.window("Pathfinder Demo",
DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT)
.opengl()
.resizable()
.allow_highdpi()
.build()
.unwrap();
gl_context = window.gl_create_context().unwrap();
gl::load_with(|name| sdl_video.gl_get_proc_address(name) as *const _);
gl_context = window.gl_create_context().unwrap();
gl::load_with(|name| sdl_video.gl_get_proc_address(name) as *const _);
event_pump = SDL_CONTEXT.with(|sdl_context| sdl_context.event_pump().unwrap());
event_pump = SDL_CONTEXT.with(|sdl_context| sdl_context.event_pump().unwrap());
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> {
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, .. } => {
Some(Event::User { message_type: type_, message_data: code as u32 })
}