Java tutorial
/* * Copyright 2015 Rui Gil * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.oceanos.shaderbox; import android.app.ActionBar; import android.content.SharedPreferences; import android.os.*; import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; import android.content.Intent; import android.database.Cursor; import android.text.*; import android.util.Log; import android.view.*; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import io.oceanos.shaderbox.database.Shader; import io.oceanos.shaderbox.database.ShaderDatabase; import io.oceanos.shaderbox.dialog.ConfirmDeleteDialogFragment; import io.oceanos.shaderbox.dialog.PropertiesDialogFragment; import io.oceanos.shaderbox.dialog.ShaderDialogListener; import io.oceanos.shaderbox.opengl.CompileResult; import io.oceanos.shaderbox.opengl.ShaderGLView; import io.oceanos.shaderbox.opengl.ShaderRenderer; public class ShaderEditorActivity extends FragmentActivity implements ShaderDialogListener, Handler.Callback { private ShaderDatabase database; private ShaderEditor editor; private ShaderGLView shaderView; private ShaderRenderer renderer; private Shader shader; private TextView fps, viewError; private Thread tUpdateFPS, tUpdateShaderCode; private CompileResult result; private boolean compileRT; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final LayoutInflater inflater = getLayoutInflater(); final View customActionBarView = inflater.inflate(R.layout.actionbar_view_save, null); final View shaderActionView = customActionBarView.findViewById(R.id.actionbar_view); final View shaderActionSave = customActionBarView.findViewById(R.id.actionbar_save); shaderActionView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (result.isSuccess()) onView(shader); else { Toast errorMsg = Toast.makeText(getBaseContext(), result.getError(), Toast.LENGTH_LONG); errorMsg.getView().setBackgroundColor(getResources().getColor(R.color.editor_color_error)); errorMsg.show(); } } }); shaderActionSave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onSave(shader); } }); fps = (TextView) customActionBarView.findViewById(R.id.text_fps); viewError = (TextView) shaderActionView.findViewById(R.id.viewError); final ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); setContentView(R.layout.activity_editor); Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); ShaderDatabase database = new ShaderDatabase(getBaseContext()); if (Intent.ACTION_SEND.equals(action) && type != null) { if ("text/plain".equals(type)) { String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); long id = database.newShader(); Cursor cursor = database.findById(id); if (cursor.moveToFirst()) shader = Shader.getValues(cursor); cursor.close(); if (sharedText != null) shader.setText(sharedText); else shader.setText(""); } } else { long id = intent.getLongExtra("ID", 0); Cursor cursor = database.findById(id); if (cursor.moveToFirst()) shader = Shader.getValues(cursor); cursor.close(); } database.close(); setSymbolListener(R.id.action_tab, '\t'); setSymbolListener(R.id.action_rpo, '('); setSymbolListener(R.id.action_rpc, ')'); setSymbolListener(R.id.action_cpo, '{'); setSymbolListener(R.id.action_cpc, '}'); setSymbolListener(R.id.action_dotcoma, ';'); setSymbolListener(R.id.action_coma, ','); setSymbolListener(R.id.action_dot, '.'); setSymbolListener(R.id.action_plus, '+'); setSymbolListener(R.id.action_minus, '-'); setSymbolListener(R.id.action_times, '*'); setSymbolListener(R.id.action_div, '/'); setSymbolListener(R.id.action_equal, '='); setSymbolListener(R.id.action_spo, '['); setSymbolListener(R.id.action_spc, ']'); setSymbolListener(R.id.action_and, '&'); setSymbolListener(R.id.action_or, '|'); setSymbolListener(R.id.action_greater, '>'); setSymbolListener(R.id.action_lesser, '<'); setSymbolListener(R.id.action_cardinal, '#'); final Handler uiHandler = new Handler(this); renderer = new ShaderRenderer(shader, uiHandler); shaderView = (ShaderGLView) findViewById(R.id.shader_view); shaderView.setRenderer(renderer); shaderView.setVRModeEnabled(false); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); int textSize = Integer.parseInt(prefs.getString("pref_editor_text_size", "14")); int opacity = Integer.parseInt(prefs.getString("pref_editor_opacity", "127")); editor = (ShaderEditor) findViewById(R.id.editor); editor.setTextSize(textSize); editor.setBackgroundColor(opacity << 24); editor.setHorizontallyScrolling(true); editor.setHorizontalScrollBarEnabled(true); editor.setText(shader.getText()); ScrollView scroll = (ScrollView) findViewById(R.id.scroll); editor.setScrollView(scroll); compileRT = prefs.getBoolean("pref_compile_rt", true); } private void setSymbolListener(int id, final char c) { findViewById(id).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { editor.getText().insert(editor.getSelectionStart(), String.valueOf(c)); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu_shader, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_delete_shader: shaderDelete(); return true; case R.id.action_copy_shader: shaderCopy(); return true; case R.id.action_share_shader: shaderShare(); return true; case R.id.action_properties_shader: shaderProperties(); return true; } return super.onOptionsItemSelected(item); } @Override public void onPause() { super.onPause(); database.close(); shaderView.onPause(); editor.onPause(); if (tUpdateFPS != null) tUpdateFPS.interrupt(); if (tUpdateShaderCode != null) { editor.removeTextChangedListener((TextWatcher) tUpdateShaderCode); tUpdateShaderCode.interrupt(); } } @Override public void onResume() { super.onResume(); database = new ShaderDatabase(getBaseContext()); shaderView.onResume(); editor.onResume(); if (shader.getPreviewMode() == 0) { shaderView.setRenderMode(ShaderGLView.RENDERMODE_WHEN_DIRTY); shaderView.requestRender(); } else { shaderView.setRenderMode(ShaderGLView.RENDERMODE_CONTINUOUSLY); tUpdateFPS = new UpdateFPS(); tUpdateFPS.start(); } if (compileRT) { tUpdateShaderCode = new UpdateShaderCode(); editor.addTextChangedListener((TextWatcher) tUpdateShaderCode); tUpdateShaderCode.start(); } } private void shaderProperties() { PropertiesDialogFragment properties = new PropertiesDialogFragment(); Bundle bundle = new Bundle(); bundle.putSerializable("shader", shader); properties.setArguments(bundle); properties.show(getSupportFragmentManager(), "Properties"); } private void shaderDelete() { Log.i(MainActivity.TAG, "delete Shader"); ConfirmDeleteDialogFragment confirm = new ConfirmDeleteDialogFragment(); Bundle bundle = new Bundle(); bundle.putSerializable("shader", shader); confirm.setArguments(bundle); confirm.show(getSupportFragmentManager(), "Confirm Delete"); } private void shaderCopy() { shader.setName("Copy of " + shader.getName()); database.insert(shader.getContentValues()); Toast.makeText(getBaseContext(), R.string.shader_copied, Toast.LENGTH_SHORT).show(); finish(); } private void shaderShare() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, shader.getText()); sendIntent.setType("text/plain"); startActivity(Intent.createChooser(sendIntent, "Send shader text to...")); } public void onSave(Shader shader) { if ((shader.getPreviewMode() == 1) && (tUpdateFPS == null)) { tUpdateFPS = new UpdateFPS(); tUpdateFPS.start(); } if ((shader.getPreviewMode() == 0) && (tUpdateFPS != null)) { fps.setText("0.0 fps"); tUpdateFPS.interrupt(); tUpdateFPS = null; } shaderView.queueEvent(thumbRequest); shaderView.queueEvent(resetShader); // compile on save if (!compileRT) { shader.setText(editor.getText().toString()); shaderView.queueEvent(compileRequest); } } public void onDelete(Shader shader) { database.delete(shader.getId()); Toast.makeText(getBaseContext(), R.string.shader_deleted, Toast.LENGTH_SHORT).show(); finish(); } public void onView(Shader shader) { Intent intent = new Intent(this, ShaderRenderActivity.class); intent.putExtra("shader", shader); startActivity(intent); } public void onCancel(Shader shader) { Log.i(MainActivity.TAG, "Dialog canceled"); } @Override public boolean handleMessage(Message message) { switch (message.what) { case ShaderRenderer.FPS_RESULT: fps.setText(String.format("%.1f fps", (float) message.obj)); return true; case ShaderRenderer.COMPILE_RESULT: result = (CompileResult) message.obj; if (!result.isSuccess()) { viewError.setCompoundDrawablesWithIntrinsicBounds(R.drawable.bug_shader, 0, 0, 0); } else { viewError.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_view, 0, 0, 0); } editor.setErrorLine(result.getErrorLine()); return true; case ShaderRenderer.THUMB_RESULT: shader.setThumb((byte[]) message.obj); database.update(shader.getContentValues()); // Render in preview is false if (shader.getPreviewMode() == 0) { shaderView.setRenderMode(ShaderGLView.RENDERMODE_WHEN_DIRTY); shaderView.requestRender(); } // Render in preview is true if (shader.getPreviewMode() == 1) shaderView.setRenderMode(ShaderGLView.RENDERMODE_CONTINUOUSLY); Toast.makeText(getBaseContext(), R.string.shader_saved, Toast.LENGTH_SHORT).show(); return true; case ShaderRenderer.LOW_FPS_RESULT: int res = shader.getResolution(); if (res != 16) { shader.setResolution(res << 1); shaderView.queueEvent(resetShader); Toast.makeText(getBaseContext(), R.string.low_fps_auto_scale, Toast.LENGTH_LONG).show(); } else { shader.setPreviewMode(0); shaderView.setRenderMode(ShaderGLView.RENDERMODE_WHEN_DIRTY); Toast.makeText(getBaseContext(), R.string.low_fps_preview_disabled, Toast.LENGTH_LONG).show(); } return true; } return false; } private final Runnable resetShader = new Runnable() { @Override public void run() { renderer.resetShader(); } }; private final Runnable thumbRequest = new Runnable() { @Override public void run() { renderer.thumbRequest(); } }; private final Runnable compileRequest = new Runnable() { @Override public void run() { renderer.compileRequest(); } }; private final Runnable fpsRequest = new Runnable() { @Override public void run() { renderer.fpsRequest(); } }; private class UpdateFPS extends Thread { @Override public void run() { final int DELAY = 1000; // 1 second // request an fps value every second while (true) { shaderView.queueEvent(fpsRequest); try { Thread.sleep(DELAY); } catch (InterruptedException e) { return; } } } } private class UpdateShaderCode extends Thread implements TextWatcher { private boolean textChanged = false; private long changeTimestamp = 0; @Override public void run() { final int DELAY = 200; // 200ms final int TEXT_DELAY = 1000; // 1 seconds while (true) { long timelapse = SystemClock.elapsedRealtime() - changeTimestamp; // request a compilation of the shader 1 second after the last text changed if (textChanged && (timelapse > TEXT_DELAY)) { shader.setText(editor.getText().toString()); if (compileRT) shaderView.queueEvent(compileRequest); textChanged = false; } // sleep try { Thread.sleep(DELAY); } catch (InterruptedException e) { return; } } } // text listener interface public void afterTextChanged(Editable editable) { changeTimestamp = SystemClock.elapsedRealtime(); textChanged = true; } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } } }