Feature: Tab switcher

This commit is contained in:
zt515
2017-06-11 21:53:48 +08:00
parent acff62db44
commit 6599700d64
7 changed files with 305 additions and 117 deletions

View File

@ -1,131 +1,131 @@
package io.neoterm
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.inputmethod.InputMethodManager
import io.neoterm.terminal.TerminalSession
import io.neoterm.view.ExtraKeysView
import io.neoterm.view.TerminalView
import io.neoterm.view.TerminalViewClient
import android.support.v4.content.ContextCompat
import android.support.v4.view.OnApplyWindowInsetsListener
import android.support.v4.view.ViewCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.View
import android.widget.ImageButton
import de.mrapp.android.tabswitcher.*
import io.neoterm.tab.TermTabDecorator
class MainActivity : Activity() {
private lateinit var extraKeysView: ExtraKeysView
class MainActivity : AppCompatActivity() {
lateinit var tabSwitcher: TabSwitcher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setContentView(R.layout.tab_main)
extraKeysView = findViewById(R.id.extra_keys) as ExtraKeysView
val view = findViewById(R.id.terminal_view) as TerminalView
view.setBackgroundColor(Color.BLACK)
view.textSize = 30
view.setTypeface(Typeface.MONOSPACE)
val session = TerminalSession("/system/bin/sh", "/",
arrayOf("/system/bin/sh"),
arrayOf("TERM=screen", "HOME=" + filesDir),
object : TerminalSession.SessionChangedCallback {
override fun onBell(session: TerminalSession?) {
}
override fun onClipboardText(session: TerminalSession?, text: String?) {
}
override fun onColorsChanged(session: TerminalSession?) {
}
override fun onSessionFinished(finishedSession: TerminalSession?) {
}
override fun onTextChanged(changedSession: TerminalSession?) {
view.onScreenUpdated()
}
override fun onTitleChanged(changedSession: TerminalSession?) {
}
})
view.setOnKeyListener(object : TerminalViewClient {
internal var mVirtualControlKeyDown: Boolean = false
internal var mVirtualFnKeyDown: Boolean = false
override fun onScale(scale: Float): Float {
if (scale < 0.9f || scale > 1.1f) {
val increase = scale > 1f
val changedSize = (if (increase) 1 else -1) * 2
view.textSize = view.textSize + changedSize
return 1.0f
}
return scale
tabSwitcher = findViewById(R.id.tab_switcher) as TabSwitcher
tabSwitcher.decorator = TermTabDecorator(this)
ViewCompat.setOnApplyWindowInsetsListener(tabSwitcher, createWindowInsetsListener())
tabSwitcher.showToolbars(true)
tabSwitcher
.setToolbarNavigationIcon(R.drawable.ic_add_box_white_24dp, createAddTabListener())
tabSwitcher.inflateToolbarMenu(R.menu.tab_switcher, createToolbarMenuListener())
tabSwitcher.addListener(object : TabSwitcherListener {
override fun onSwitcherShown(tabSwitcher: TabSwitcher) {
}
override fun onSingleTapUp(e: MotionEvent?) {
(getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
override fun onSwitcherHidden(tabSwitcher: TabSwitcher) {
}
override fun shouldBackButtonBeMappedToEscape(): Boolean {
return false
override fun onSelectionChanged(tabSwitcher: TabSwitcher, selectedTabIndex: Int, selectedTab: Tab?) {
}
override fun copyModeChanged(copyMode: Boolean) {
// TODO
override fun onTabAdded(tabSwitcher: TabSwitcher, index: Int, tab: Tab, animation: Animation) {
}
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
// TODO
return false
override fun onTabRemoved(tabSwitcher: TabSwitcher, index: Int, tab: Tab, animation: Animation) {
}
override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean {
return handleVirtualKeys(keyCode, e, false)
override fun onAllTabsRemoved(tabSwitcher: TabSwitcher, tabs: Array<out Tab>, animation: Animation) {
}
override fun readControlKey(): Boolean {
return extraKeysView.readControlButton() || mVirtualControlKeyDown
}
override fun readAltKey(): Boolean {
return extraKeysView.readAltButton() || mVirtualFnKeyDown
}
override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean {
// TODO
return false
}
override fun onLongPress(event: MotionEvent?): Boolean {
// TODO
return false
}
private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean {
if (event == null) {
return false
}
val inputDevice = event.device
if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
return false
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
mVirtualControlKeyDown = down
return true
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mVirtualFnKeyDown = down
return true
}
return false
}
})
view.attachSession(session)
}
fun createAddTabListener(): View.OnClickListener {
return View.OnClickListener {
val index = tabSwitcher.count
val animation = createRevealAnimation()
tabSwitcher.addTab(createTab(index), 0, animation)
}
}
fun createToolbarMenuListener(): Toolbar.OnMenuItemClickListener {
return Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.add_tab_menu_item -> {
val index = tabSwitcher.count
val tab = createTab(index)
if (tabSwitcher.isSwitcherShown) {
tabSwitcher.addTab(tab, 0, createRevealAnimation())
} else {
tabSwitcher.addTab(tab, 0, createPeekAnimation())
}
true
}
else -> false
}
}
}
private fun createTab(index: Int): Tab {
val tab = Tab("Neo Term #" + index)
tab.isCloseable = true
tab.parameters = Bundle()
tab.parameters?.putInt("type", TermTabDecorator.TYPE_NEW)
tab.setBackgroundColor(ContextCompat.getColor(this, R.color.tab_background_color))
tab.setTitleTextColor(ContextCompat.getColor(this, R.color.tab_title_text_color))
return tab
}
private fun createRevealAnimation(): Animation {
var x = 0f
var y = 0f
val view = getNavigationMenuItem()
if (view != null) {
val location = IntArray(2)
view.getLocationInWindow(location)
x = location[0] + view.width / 2f
y = location[1] + view.height / 2f
}
return RevealAnimation.Builder().setX(x).setY(y).create()
}
private fun createPeekAnimation(): Animation {
return PeekAnimation.Builder().setX(tabSwitcher.width / 2f).create()
}
private fun getNavigationMenuItem(): View? {
val toolbars = tabSwitcher.toolbars
if (toolbars != null) {
val toolbar = if (toolbars.size > 1) toolbars[1] else toolbars[0]
val size = toolbar.childCount
(0..size - 1)
.map { toolbar.getChildAt(it) }
.filterIsInstance<ImageButton>()
.forEach { return it }
}
return null
}
private fun createWindowInsetsListener(): OnApplyWindowInsetsListener {
return OnApplyWindowInsetsListener { v, insets ->
tabSwitcher.setPadding(insets.systemWindowInsetLeft,
insets.systemWindowInsetTop, insets.systemWindowInsetRight,
insets.systemWindowInsetBottom)
insets
}
}
}

View File

@ -0,0 +1,168 @@
package io.neoterm.tab
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.support.v7.widget.Toolbar
import android.view.*
import android.view.inputmethod.InputMethodManager
import de.mrapp.android.tabswitcher.Tab
import de.mrapp.android.tabswitcher.TabSwitcher
import de.mrapp.android.tabswitcher.TabSwitcherDecorator
import io.neoterm.MainActivity
import io.neoterm.R
import io.neoterm.terminal.TerminalSession
import io.neoterm.view.ExtraKeysView
import io.neoterm.view.TerminalView
import io.neoterm.view.TerminalViewClient
/**
* @author kiva
*/
class TermTabDecorator(val context: MainActivity) : TabSwitcherDecorator() {
override fun onInflateView(inflater: LayoutInflater, parent: ViewGroup?, viewType: Int): View {
val view = inflater.inflate(R.layout.term, parent, false)
val toolbar = view.findViewById(R.id.terminal_toolbar) as Toolbar
toolbar.inflateMenu(R.menu.tab_switcher)
toolbar.setOnMenuItemClickListener(context.createToolbarMenuListener())
val menu = toolbar.menu
TabSwitcher.setupWithMenu(context.tabSwitcher, menu, {
context.tabSwitcher.showSwitcher()
})
return view
}
override fun onShowTab(context: Context, tabSwitcher: TabSwitcher,
view: View, tab: Tab, index: Int, viewType: Int, savedInstanceState: Bundle?) {
val toolbar = findViewById<Toolbar>(R.id.terminal_toolbar)
toolbar?.title = tab.title
val terminalView = findViewById<TerminalView>(R.id.terminal_view)
val extraKeysView = findViewById<ExtraKeysView>(R.id.extra_keys)
setupTerminalView(terminalView, extraKeysView)
}
private fun setupTerminalView(view: TerminalView?, extraKeysView: ExtraKeysView?) {
if (view == null) {
return
}
view.setBackgroundColor(Color.BLACK)
view.textSize = 30
view.setTypeface(Typeface.MONOSPACE)
val session = TerminalSession("/system/bin/sh", "/",
arrayOf("/system/bin/sh"),
arrayOf("TERM=screen", "HOME=" + context.filesDir),
object : TerminalSession.SessionChangedCallback {
override fun onBell(session: TerminalSession?) {
}
override fun onClipboardText(session: TerminalSession?, text: String?) {
}
override fun onColorsChanged(session: TerminalSession?) {
}
override fun onSessionFinished(finishedSession: TerminalSession?) {
}
override fun onTextChanged(changedSession: TerminalSession?) {
view.onScreenUpdated()
}
override fun onTitleChanged(changedSession: TerminalSession?) {
}
})
view.setOnKeyListener(object : TerminalViewClient {
internal var mVirtualControlKeyDown: Boolean = false
internal var mVirtualFnKeyDown: Boolean = false
override fun onScale(scale: Float): Float {
if (scale < 0.9f || scale > 1.1f) {
val increase = scale > 1f
val changedSize = (if (increase) 1 else -1) * 2
view.textSize = view.textSize + changedSize
return 1.0f
}
return scale
}
override fun onSingleTapUp(e: MotionEvent?) {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
override fun shouldBackButtonBeMappedToEscape(): Boolean {
return false
}
override fun copyModeChanged(copyMode: Boolean) {
// TODO
}
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
// TODO
return false
}
override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean {
return handleVirtualKeys(keyCode, e, false)
}
override fun readControlKey(): Boolean {
return (extraKeysView != null && extraKeysView.readControlButton()) || mVirtualControlKeyDown
}
override fun readAltKey(): Boolean {
return (extraKeysView != null && extraKeysView.readAltButton()) || mVirtualFnKeyDown
}
override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean {
// TODO
return false
}
override fun onLongPress(event: MotionEvent?): Boolean {
// TODO
return false
}
private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean {
if (event == null) {
return false
}
val inputDevice = event.device
if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
return false
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
mVirtualControlKeyDown = down
return true
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mVirtualFnKeyDown = down
return true
}
return false
}
})
view.attachSession(session)
}
override fun getViewTypeCount(): Int {
return 2
}
override fun getViewType(tab: Tab, index: Int): Int {
return tab.parameters?.getInt("type")!!
}
companion object {
val TYPE_LOADED = 1
val TYPE_NEW = 0
}
}