This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
/
BrowserMenuController.kt
141 lines (124 loc) · 4.85 KB
/
BrowserMenuController.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.menu2
import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.PopupWindow
import mozilla.components.browser.menu2.ext.displayPopup
import mozilla.components.browser.menu2.view.MenuView
import mozilla.components.concept.menu.MenuController
import mozilla.components.concept.menu.MenuStyle
import mozilla.components.concept.menu.Orientation
import mozilla.components.concept.menu.Side
import mozilla.components.concept.menu.candidate.MenuCandidate
import mozilla.components.concept.menu.candidate.NestedMenuCandidate
import mozilla.components.concept.menu.ext.findNestedMenuCandidate
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
/**
* Controls a popup menu composed of MenuCandidate objects.
* @param visibleSide Sets the menu to open with either the start or end visible.
* @param style Custom styling for this menu controller.
*/
class BrowserMenuController(
private val visibleSide: Side = Side.START,
private val style: MenuStyle? = null
) : MenuController, Observable<MenuController.Observer> by ObserverRegistry() {
private var currentPopupInfo: PopupMenuInfo? = null
private var menuCandidates: List<MenuCandidate> = emptyList()
private val menuDismissListener = PopupWindow.OnDismissListener {
currentPopupInfo = null
notifyObservers { onDismiss() }
}
/**
* @param anchor The view on which to pin the popup window.
* @param orientation The preferred orientation to show the popup window.
* @param forceOrientation When set to true, the orientation will be respected even when the
* menu doesn't fully fit.
*/
override fun show(
anchor: View,
orientation: Orientation?,
forceOrientation: Boolean,
): PopupWindow {
val view = MenuView(anchor.context).apply {
// Show nested list if present, or the standard menu candidates list.
submitList(menuCandidates)
setVisibleSide(visibleSide)
style?.let { setStyle(it) }
}
return MenuPopupWindow(view).apply {
view.onDismiss = ::dismiss
view.onReopenMenu = ::reopenMenu
setOnDismissListener(menuDismissListener)
displayPopup(view, anchor, orientation, forceOrientation)
}.also {
currentPopupInfo = PopupMenuInfo(
window = it,
anchor = anchor,
orientation = orientation,
nested = null
)
}
}
/**
* Re-opens the menu and displays the given nested list.
* No-op if the menu is not yet open.
*/
private fun reopenMenu(nested: NestedMenuCandidate?) {
val info = currentPopupInfo ?: return
info.window.run {
// Dismiss silently
setOnDismissListener(null)
dismiss()
setOnDismissListener(menuDismissListener)
// Quickly remove the current list
view.submitList(null)
// Display the new nested list
view.submitList(nested?.subMenuItems ?: menuCandidates)
// Reopen the menu
displayPopup(view, info.anchor, info.orientation)
}
currentPopupInfo = info.copy(nested = nested)
}
/**
* Dismiss the menu popup if the menu is visible.
*/
override fun dismiss() {
currentPopupInfo?.window?.dismiss()
}
/**
* Changes the contents of the menu.
*/
override fun submitList(list: List<MenuCandidate>) {
menuCandidates = list
val info = currentPopupInfo
// If menu is already open, update the displayed items
if (info != null) {
// If a nested menu is open, it should be displayed
val displayedItems = if (info.nested != null) {
list.findNestedMenuCandidate(info.nested.id)?.subMenuItems
} else {
list
}
// If the new menu is null, close & reopen the popup on the main list
if (displayedItems == null) {
// close & reopen popup
reopenMenu(nested = null)
} else {
info.window.view.submitList(displayedItems)
}
}
notifyObservers { onMenuListSubmit(list) }
}
private class MenuPopupWindow(
val view: MenuView
) : PopupWindow(view, WRAP_CONTENT, WRAP_CONTENT, true)
private data class PopupMenuInfo(
val window: MenuPopupWindow,
val anchor: View,
val orientation: Orientation?,
val nested: NestedMenuCandidate? = null
)
}