Home | History | Annotate | Download | only in viewer
      1 package org.skia.viewer;
      2 
      3 import android.text.Editable;
      4 import android.text.TextWatcher;
      5 import android.view.LayoutInflater;
      6 import android.view.SurfaceView;
      7 import android.view.View;
      8 import android.view.ViewGroup;
      9 import android.widget.AdapterView;
     10 import android.widget.BaseAdapter;
     11 import android.widget.CompoundButton;
     12 import android.widget.EditText;
     13 import android.widget.LinearLayout;
     14 import android.widget.Spinner;
     15 import android.widget.Switch;
     16 import android.widget.TextView;
     17 
     18 import org.json.JSONArray;
     19 import org.json.JSONException;
     20 import org.json.JSONObject;
     21 
     22 import java.util.ArrayList;
     23 
     24 /*
     25     The navigation drawer requires ListView, so we implemented this BaseAdapter for that ListView.
     26     However, the ListView does not provide good support for updating just a single child view.
     27     For example, a frequently changed child view such as FPS state will reset the spinner of
     28     all other child views; although I didn't change other child views and directly return
     29     the convertView in BaseAdapter.getView(int position, View convertView, ViewGroup parent).
     30 
     31     Therefore, our adapter only returns one LinearLayout for the ListView.
     32     Within that LinearLayout, we maintain views ourselves so we can efficiently update its children.
     33  */
     34 public class StateAdapter extends BaseAdapter implements AdapterView.OnItemSelectedListener {
     35     private static final String NAME = "name";
     36     private static final String VALUE = "value";
     37     private static final String OPTIONS = "options";
     38     private static final String BACKEND_STATE_NAME = "Backend";
     39     private static final String FPS_STATE_NAME = "FPS";
     40     private static final String REFRESH_STATE_NAME = "Refresh";
     41     private static final String ON = "ON";
     42     private static final String OFF = "OFF";
     43     private static final int FILTER_LENGTH = 20;
     44 
     45     private ViewerActivity mViewerActivity;
     46     private LinearLayout mLayout;
     47     private JSONArray mStateJson;
     48     private TextView mFPSFloatText;
     49 
     50     public StateAdapter(ViewerActivity viewerActivity) {
     51         mViewerActivity = viewerActivity;
     52         mFPSFloatText = (TextView) viewerActivity.findViewById(R.id.fpsFloatText);
     53         try {
     54             mStateJson = new JSONArray("[{\"name\": \"Please\", " +
     55                     "\"value\": \"Initialize\", \"options\": []}]");
     56         } catch (JSONException e) {
     57             e.printStackTrace();
     58         }
     59     }
     60 
     61     public void setState(String stateJson) {
     62         try {
     63             mStateJson = new JSONArray(stateJson);
     64             if (mLayout != null) {
     65                 updateDrawer();
     66             } else {
     67                 notifyDataSetChanged();
     68             }
     69         } catch (JSONException e) {
     70             e.printStackTrace();
     71         }
     72     }
     73 
     74     // The first list item is the mLayout that contains a list of state items
     75     // The second list item is the toggle for float FPS
     76     @Override
     77     public int getCount() {
     78         return 2;
     79     }
     80 
     81     @Override
     82     public Object getItem(int position) {
     83         return null;
     84     }
     85 
     86     @Override
     87     public long getItemId(int position) {
     88         return 0;
     89     }
     90 
     91     @Override
     92     public View getView(int position, View convertView, ViewGroup parent) {
     93         switch (position) {
     94             case 0: {
     95                 if (mLayout == null) {
     96                     mLayout = new LinearLayout(mViewerActivity);
     97                     mLayout.setOrientation(LinearLayout.VERTICAL);
     98                     updateDrawer();
     99                 }
    100                 return mLayout;
    101             }
    102             case 1: {
    103                 View view = LayoutInflater.from(mViewerActivity).inflate(R.layout.fps_toggle, null);
    104                 Switch theSwitch = (Switch) view.findViewById(R.id.theSwitch);
    105                 theSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
    106                     @Override
    107                     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    108                         mFPSFloatText.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE);
    109                         // Quickly set the bool fRefresh in native Viewer app for continuous refresh
    110                         mViewerActivity.onStateChanged(REFRESH_STATE_NAME, isChecked ? ON : OFF);
    111                     }
    112                 });
    113                 return view;
    114             }
    115             default: {
    116                 return null;
    117             }
    118         }
    119     }
    120 
    121     private void populateView(JSONObject item, View view) throws JSONException {
    122         LinearLayout itemView = (LinearLayout) view;
    123         TextView nameText = (TextView) itemView.findViewById(R.id.nameText);
    124         TextView valueText = (TextView) itemView.findViewById(R.id.valueText);
    125         Spinner optionSpinner = (Spinner) itemView.findViewById(R.id.optionSpinner);
    126 
    127         String value = item.getString(VALUE);
    128         itemView.setTag(item.toString()); // To save unnecessary view update
    129         itemView.setTag(R.integer.value_tag_key, value);
    130 
    131         nameText.setText(item.getString(NAME));
    132 
    133         if (nameText.getText().equals(FPS_STATE_NAME) && mFPSFloatText != null) {
    134             mFPSFloatText.setText(value);
    135             // Don't show FPS in the drawer. We'll show it in the float text.
    136             itemView.setVisibility(View.GONE);
    137         }
    138 
    139         JSONArray options = item.getJSONArray(OPTIONS);
    140         if (options.length() == 0) {
    141             valueText.setText(value);
    142             valueText.setVisibility(View.VISIBLE);
    143             optionSpinner.setVisibility(View.GONE);
    144         } else {
    145             ArrayList<String> optionList = new ArrayList<>();
    146             String[] optionStrings = new String[options.length()];
    147             for (int j = 0; j < options.length(); j++) {
    148                 optionList.add(options.getString(j));
    149             }
    150             final OptionAdapter adapter = new OptionAdapter(mViewerActivity,
    151                     android.R.layout.simple_spinner_dropdown_item, optionList, optionSpinner);
    152             adapter.setCurrentOption(value);
    153             optionSpinner.setAdapter(adapter);
    154             if (optionStrings.length >= FILTER_LENGTH) {
    155                 View existingView = itemView.getChildAt(1);
    156                 if (!(existingView instanceof EditText)) {
    157                     EditText filterText = new EditText(mViewerActivity);
    158                     filterText.setHint("Filter");
    159                     itemView.addView(filterText, 1);
    160                     filterText.addTextChangedListener(new TextWatcher() {
    161                         @Override
    162                         public void beforeTextChanged(CharSequence s, int start, int cnt,
    163                                 int after) {
    164                         }
    165 
    166                         @Override
    167                         public void onTextChanged(CharSequence s, int start, int before, int cnt) {
    168                         }
    169 
    170                         @Override
    171                         public void afterTextChanged(Editable s) {
    172                             adapter.getFilter().filter(s.toString());
    173                         }
    174                     });
    175                 }
    176             }
    177             optionSpinner.setSelection(optionList.indexOf(value));
    178             optionSpinner.setOnItemSelectedListener(this);
    179             optionSpinner.setVisibility(View.VISIBLE);
    180             valueText.setVisibility(View.GONE);
    181         }
    182     }
    183     private View inflateItemView(JSONObject item) throws JSONException {
    184         LinearLayout itemView = (LinearLayout)
    185                 LayoutInflater.from(mViewerActivity).inflate(R.layout.state_item, null);
    186         populateView(item, itemView);
    187         return itemView;
    188     }
    189 
    190     private void updateDrawer() {
    191         try {
    192             if (mStateJson.length() < mLayout.getChildCount()) {
    193                 mLayout.removeViews(
    194                         mStateJson.length(), mLayout.getChildCount() - mStateJson.length());
    195             }
    196             for (int i = 0; i < mStateJson.length(); i++) {
    197                 JSONObject stateObject = mStateJson.getJSONObject(i);
    198                 View childView = mLayout.getChildAt(i);
    199                 if (childView != null) {
    200                     if (stateObject.toString().equals(childView.getTag())) {
    201                         continue; // No update, reuse the old view and skip the remaining step
    202                     } else {
    203                         populateView(stateObject, childView);
    204                     }
    205                 } else {
    206                     mLayout.addView(inflateItemView(stateObject), i);
    207                 }
    208             }
    209         } catch (JSONException e) {
    210             e.printStackTrace();
    211         }
    212     }
    213 
    214     @Override
    215     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    216         if (view == null) {
    217             return;
    218         }
    219         View stateItem = (View) parent.getParent();
    220         String stateName = ((TextView) stateItem.findViewById(R.id.nameText)).getText().toString();
    221         String stateValue = ((TextView) view).getText().toString();
    222         if (!stateValue.equals(stateItem.getTag(R.integer.value_tag_key))) {
    223             stateItem.setTag(null); // Reset the tag to let updateDrawer update this item view.
    224             mViewerActivity.onStateChanged(stateName, stateValue);
    225         }
    226 
    227         // Due to the current Android limitation, we're required to recreate the SurfaceView for
    228         // switching to/from the Raster backend.
    229         // (Although we can switch between GPU backend without recreating the SurfaceView.)
    230         final Object oldValue = stateItem.getTag(R.integer.value_tag_key);
    231         if (stateName.equals(BACKEND_STATE_NAME)
    232                 && oldValue != null && !stateValue.equals(oldValue)) {
    233             LinearLayout mainLayout = (LinearLayout) mViewerActivity.findViewById(R.id.mainLayout);
    234             mainLayout.removeAllViews();
    235             SurfaceView surfaceView = new SurfaceView(mViewerActivity);
    236             surfaceView.setId(R.id.surfaceView);
    237             surfaceView.getHolder().addCallback(mViewerActivity);
    238             surfaceView.setOnTouchListener(mViewerActivity);
    239             mainLayout.addView(surfaceView);
    240         }
    241     }
    242 
    243     @Override
    244     public void onNothingSelected(AdapterView<?> parent) {
    245         // do nothing
    246     }
    247 }
    248