@openeditor/react-native-prose-editor 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +160 -0
- package/README.md +180 -0
- package/android/build.gradle +69 -0
- package/android/consumer-rules.pro +18 -0
- package/android/src/androidTest/AndroidManifest.xml +8 -0
- package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +53 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +241 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +338 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +789 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +350 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +5 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +50 -0
- package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +135 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +4527 -0
- package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +999 -0
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +474 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +3137 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +561 -0
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +1413 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +133 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +2172 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +399 -0
- package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +482 -0
- package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +137 -0
- package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +321 -0
- package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +4202 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +2581 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +27 -0
- package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +197 -0
- package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +84 -0
- package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +544 -0
- package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +461 -0
- package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +2054 -0
- package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +1367 -0
- package/app.plugin.js +62 -0
- package/dist/EditorTheme.d.ts +113 -0
- package/dist/EditorTheme.js +29 -0
- package/dist/EditorToolbar.d.ts +191 -0
- package/dist/EditorToolbar.js +1095 -0
- package/dist/NativeEditorBridge.d.ts +325 -0
- package/dist/NativeEditorBridge.js +933 -0
- package/dist/NativeProseViewer.d.ts +53 -0
- package/dist/NativeProseViewer.js +480 -0
- package/dist/NativeRichTextEditor.d.ts +183 -0
- package/dist/NativeRichTextEditor.js +2574 -0
- package/dist/YjsCollaboration.d.ts +90 -0
- package/dist/YjsCollaboration.js +743 -0
- package/dist/addons.d.ts +100 -0
- package/dist/addons.js +82 -0
- package/dist/heightCache.d.ts +6 -0
- package/dist/heightCache.js +45 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +30 -0
- package/dist/schemas.d.ts +37 -0
- package/dist/schemas.js +263 -0
- package/dist/useNativeEditor.d.ts +42 -0
- package/dist/useNativeEditor.js +124 -0
- package/expo-module.config.json +9 -0
- package/ios/EditorAddons.swift +327 -0
- package/ios/EditorCore.xcframework/Info.plist +44 -0
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorLayoutManager.swift +774 -0
- package/ios/EditorTheme.swift +515 -0
- package/ios/Generated_editor_core.swift +1513 -0
- package/ios/NativeEditorExpoView.swift +3593 -0
- package/ios/NativeEditorModule.swift +624 -0
- package/ios/NativeProseViewerExpoView.swift +276 -0
- package/ios/PositionBridge.swift +558 -0
- package/ios/ReactNativeProseEditor.podspec +49 -0
- package/ios/RenderBridge.swift +1708 -0
- package/ios/RichTextEditorView.swift +6150 -0
- package/ios/Tests/NativePerformanceTests.swift +687 -0
- package/ios/Tests/PositionBridgeTests.swift +706 -0
- package/ios/Tests/RenderBridgeTests.swift +2236 -0
- package/ios/Tests/RichTextEditorViewTests.swift +6548 -0
- package/ios/editor_coreFFI/editor_coreFFI.h +1289 -0
- package/ios/editor_coreFFI/module.modulemap +7 -0
- package/ios/editor_coreFFI.h +904 -0
- package/ios/editor_coreFFI.modulemap +7 -0
- package/package.json +74 -0
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2563 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
package com.openeditor.editor
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.RectF
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.util.AttributeSet
|
|
8
|
+
import android.view.MotionEvent
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import android.widget.FrameLayout
|
|
11
|
+
import android.widget.LinearLayout
|
|
12
|
+
import android.widget.ScrollView
|
|
13
|
+
import uniffi.editor_core.*
|
|
14
|
+
|
|
15
|
+
/** Container view that owns the native editor text field. */
|
|
16
|
+
class RichTextEditorView @JvmOverloads constructor(
|
|
17
|
+
context: Context,
|
|
18
|
+
attrs: AttributeSet? = null,
|
|
19
|
+
defStyleAttr: Int = 0
|
|
20
|
+
) : LinearLayout(context, attrs, defStyleAttr) {
|
|
21
|
+
val editorViewport: FrameLayout
|
|
22
|
+
|
|
23
|
+
private class EditorScrollView(context: Context) : ScrollView(context) {
|
|
24
|
+
private fun updateParentIntercept(action: Int) {
|
|
25
|
+
val canScroll = canScrollVertically(-1) || canScrollVertically(1)
|
|
26
|
+
if (!canScroll) return
|
|
27
|
+
when (action) {
|
|
28
|
+
MotionEvent.ACTION_DOWN,
|
|
29
|
+
MotionEvent.ACTION_MOVE -> parent?.requestDisallowInterceptTouchEvent(true)
|
|
30
|
+
MotionEvent.ACTION_UP,
|
|
31
|
+
MotionEvent.ACTION_CANCEL -> parent?.requestDisallowInterceptTouchEvent(false)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
|
36
|
+
updateParentIntercept(ev.actionMasked)
|
|
37
|
+
return super.onInterceptTouchEvent(ev)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
|
41
|
+
updateParentIntercept(ev.actionMasked)
|
|
42
|
+
return super.onTouchEvent(ev)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
val editorEditText: EditorEditText
|
|
47
|
+
val editorScrollView: ScrollView
|
|
48
|
+
private val remoteSelectionOverlayView: RemoteSelectionOverlayView
|
|
49
|
+
private val imageResizeOverlayView: ImageResizeOverlayView
|
|
50
|
+
|
|
51
|
+
private var heightBehavior: EditorHeightBehavior = EditorHeightBehavior.FIXED
|
|
52
|
+
private var imageResizingEnabled = true
|
|
53
|
+
private var theme: EditorTheme? = null
|
|
54
|
+
private var baseBackgroundColor: Int = Color.WHITE
|
|
55
|
+
private var viewportBottomInsetPx: Int = 0
|
|
56
|
+
internal var appliedCornerRadiusPx: Float = 0f
|
|
57
|
+
internal var appliedBackgroundColorForTesting: Int = Color.WHITE
|
|
58
|
+
|
|
59
|
+
private var currentEditorId: Long = 0
|
|
60
|
+
private var deferEditorUnbindOnDetach = false
|
|
61
|
+
internal var onBeforeDetachedFromWindow: (() -> Unit)? = null
|
|
62
|
+
|
|
63
|
+
/** Binds or unbinds the Rust editor instance. */
|
|
64
|
+
var editorId: Long
|
|
65
|
+
get() = currentEditorId
|
|
66
|
+
set(value) {
|
|
67
|
+
setEditorId(value, bindEditor = true)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
init {
|
|
71
|
+
orientation = VERTICAL
|
|
72
|
+
|
|
73
|
+
editorEditText = EditorEditText(context)
|
|
74
|
+
editorScrollView = EditorScrollView(context).apply {
|
|
75
|
+
clipToPadding = false
|
|
76
|
+
isFillViewport = false
|
|
77
|
+
}
|
|
78
|
+
editorViewport = FrameLayout(context)
|
|
79
|
+
remoteSelectionOverlayView = RemoteSelectionOverlayView(context)
|
|
80
|
+
imageResizeOverlayView = ImageResizeOverlayView(context)
|
|
81
|
+
editorScrollView.addView(editorEditText, createEditorLayoutParams())
|
|
82
|
+
editorViewport.addView(
|
|
83
|
+
editorScrollView,
|
|
84
|
+
FrameLayout.LayoutParams(
|
|
85
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
86
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
editorViewport.addView(
|
|
90
|
+
remoteSelectionOverlayView,
|
|
91
|
+
FrameLayout.LayoutParams(
|
|
92
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
93
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
editorViewport.addView(
|
|
97
|
+
imageResizeOverlayView,
|
|
98
|
+
FrameLayout.LayoutParams(
|
|
99
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
100
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
remoteSelectionOverlayView.bind(this)
|
|
104
|
+
imageResizeOverlayView.bind(this)
|
|
105
|
+
editorScrollView.setOnScrollChangeListener { _, _, _, _, _ ->
|
|
106
|
+
refreshOverlays()
|
|
107
|
+
}
|
|
108
|
+
editorEditText.onSelectionOrContentMayChange = { refreshOverlays() }
|
|
109
|
+
|
|
110
|
+
addView(editorViewport, createContainerLayoutParams())
|
|
111
|
+
updateScrollContainerAppearance()
|
|
112
|
+
updateScrollContainerInsets()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fun configure(
|
|
116
|
+
textSizePx: Float = 16f * resources.displayMetrics.density,
|
|
117
|
+
textColor: Int = Color.BLACK,
|
|
118
|
+
backgroundColor: Int = Color.WHITE
|
|
119
|
+
) {
|
|
120
|
+
baseBackgroundColor = backgroundColor
|
|
121
|
+
editorEditText.setBaseStyle(textSizePx, textColor, backgroundColor)
|
|
122
|
+
updateScrollContainerAppearance()
|
|
123
|
+
refreshOverlays()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fun applyTheme(theme: EditorTheme?) {
|
|
127
|
+
this.theme = theme
|
|
128
|
+
val previousScrollY = editorScrollView.scrollY
|
|
129
|
+
editorEditText.applyTheme(theme)
|
|
130
|
+
updateScrollContainerAppearance()
|
|
131
|
+
updateScrollContainerInsets()
|
|
132
|
+
if (heightBehavior == EditorHeightBehavior.FIXED) {
|
|
133
|
+
editorScrollView.post {
|
|
134
|
+
val childHeight = editorScrollView.getChildAt(0)?.height ?: 0
|
|
135
|
+
val maxScrollY = maxOf(
|
|
136
|
+
0,
|
|
137
|
+
childHeight + editorScrollView.paddingTop + editorScrollView.paddingBottom - editorScrollView.height
|
|
138
|
+
)
|
|
139
|
+
editorScrollView.scrollTo(0, previousScrollY.coerceIn(0, maxScrollY))
|
|
140
|
+
refreshOverlays()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
refreshOverlays()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun setHeightBehavior(heightBehavior: EditorHeightBehavior) {
|
|
147
|
+
if (this.heightBehavior == heightBehavior) return
|
|
148
|
+
this.heightBehavior = heightBehavior
|
|
149
|
+
editorEditText.setHeightBehavior(heightBehavior)
|
|
150
|
+
editorEditText.layoutParams = createEditorLayoutParams()
|
|
151
|
+
editorViewport.layoutParams = createContainerLayoutParams()
|
|
152
|
+
editorScrollView.isVerticalScrollBarEnabled = heightBehavior == EditorHeightBehavior.FIXED
|
|
153
|
+
editorScrollView.overScrollMode = if (heightBehavior == EditorHeightBehavior.FIXED) {
|
|
154
|
+
OVER_SCROLL_IF_CONTENT_SCROLLS
|
|
155
|
+
} else {
|
|
156
|
+
OVER_SCROLL_NEVER
|
|
157
|
+
}
|
|
158
|
+
updateScrollContainerInsets()
|
|
159
|
+
refreshOverlays()
|
|
160
|
+
requestLayout()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fun setImageResizingEnabled(enabled: Boolean) {
|
|
164
|
+
if (imageResizingEnabled == enabled) return
|
|
165
|
+
imageResizingEnabled = enabled
|
|
166
|
+
editorEditText.setImageResizingEnabled(enabled)
|
|
167
|
+
refreshOverlays()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fun setViewportBottomInsetPx(bottomInsetPx: Int) {
|
|
171
|
+
val clampedInset = bottomInsetPx.coerceAtLeast(0)
|
|
172
|
+
if (viewportBottomInsetPx == clampedInset) return
|
|
173
|
+
viewportBottomInsetPx = clampedInset
|
|
174
|
+
updateScrollContainerInsets()
|
|
175
|
+
editorEditText.setViewportBottomInsetPx(clampedInset)
|
|
176
|
+
refreshOverlays()
|
|
177
|
+
requestLayout()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
internal fun viewportBottomInsetPxForTesting(): Int = viewportBottomInsetPx
|
|
181
|
+
|
|
182
|
+
fun setRemoteSelections(selections: List<RemoteSelectionDecoration>) {
|
|
183
|
+
remoteSelectionOverlayView.setRemoteSelections(selections)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fun refreshRemoteSelections() {
|
|
187
|
+
if (!remoteSelectionOverlayView.hasSelectionsOrCachedGeometry()) return
|
|
188
|
+
remoteSelectionOverlayView.refreshGeometry()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fun imageResizeOverlayRectForTesting(): android.graphics.RectF? =
|
|
192
|
+
imageResizeOverlayView.visibleRectForTesting()
|
|
193
|
+
|
|
194
|
+
fun resizeSelectedImageForTesting(widthPx: Float, heightPx: Float) {
|
|
195
|
+
imageResizeOverlayView.simulateResizeForTesting(widthPx, heightPx)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fun remoteSelectionDebugSnapshotsForTesting(): List<RemoteSelectionDebugSnapshot> =
|
|
199
|
+
remoteSelectionOverlayView.debugSnapshotsForTesting()
|
|
200
|
+
|
|
201
|
+
fun setRemoteSelectionScalarResolverForTesting(resolver: (Long, Int) -> Int) {
|
|
202
|
+
remoteSelectionOverlayView.docToScalarResolver = resolver
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fun setRemoteSelectionEditorIdForTesting(editorId: Long) {
|
|
206
|
+
remoteSelectionOverlayView.editorIdOverrideForTesting = editorId
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun setContent(html: String) {
|
|
210
|
+
if (editorId == 0L) return
|
|
211
|
+
editorSetHtml(editorId.toULong(), html)
|
|
212
|
+
editorEditText.applyUpdateJSON(
|
|
213
|
+
editorGetCurrentState(editorId.toULong()),
|
|
214
|
+
notifyListener = false,
|
|
215
|
+
refreshInputConnectionForExternalUpdate = true
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fun setContent(json: org.json.JSONObject) {
|
|
220
|
+
if (editorId == 0L) return
|
|
221
|
+
editorSetJson(editorId.toULong(), json.toString())
|
|
222
|
+
editorEditText.applyUpdateJSON(
|
|
223
|
+
editorGetCurrentState(editorId.toULong()),
|
|
224
|
+
notifyListener = false,
|
|
225
|
+
refreshInputConnectionForExternalUpdate = true
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
internal fun rebindEditorIfNeeded(notifyListener: Boolean = true) {
|
|
230
|
+
if (editorId != 0L && editorEditText.editorId != editorId) {
|
|
231
|
+
setEditorId(editorId, bindEditor = true, notifyListener = notifyListener)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
internal fun setEditorIdWhileDetached(value: Long) {
|
|
236
|
+
setEditorId(value, bindEditor = false)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
internal fun deferEditorUnbindOnNextDetach() {
|
|
240
|
+
deferEditorUnbindOnDetach = true
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
internal fun clearDeferredEditorUnbind() {
|
|
244
|
+
deferEditorUnbindOnDetach = false
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
internal fun unbindEditorForDetachedViewIfNeeded() {
|
|
248
|
+
if (isAttachedToWindow) return
|
|
249
|
+
deferEditorUnbindOnDetach = false
|
|
250
|
+
if (editorId != 0L) {
|
|
251
|
+
editorEditText.unbindEditor()
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private fun setEditorId(value: Long, bindEditor: Boolean, notifyListener: Boolean = true) {
|
|
256
|
+
val targetBoundEditorId = if (bindEditor) value else 0L
|
|
257
|
+
if (currentEditorId == value && editorEditText.editorId == targetBoundEditorId) return
|
|
258
|
+
if (currentEditorId != value || editorEditText.editorId != targetBoundEditorId) {
|
|
259
|
+
editorEditText.discardTransientNativeInputForEditorRebind()
|
|
260
|
+
}
|
|
261
|
+
currentEditorId = value
|
|
262
|
+
if (bindEditor && value != 0L) {
|
|
263
|
+
editorEditText.bindEditor(value, notifyListener = notifyListener)
|
|
264
|
+
} else {
|
|
265
|
+
editorEditText.unbindEditor()
|
|
266
|
+
}
|
|
267
|
+
refreshOverlays()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override fun onAttachedToWindow() {
|
|
271
|
+
super.onAttachedToWindow()
|
|
272
|
+
clearDeferredEditorUnbind()
|
|
273
|
+
rebindEditorIfNeeded()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
override fun onDetachedFromWindow() {
|
|
277
|
+
onBeforeDetachedFromWindow?.invoke()
|
|
278
|
+
super.onDetachedFromWindow()
|
|
279
|
+
if (editorId != 0L && !deferEditorUnbindOnDetach) {
|
|
280
|
+
editorEditText.unbindEditor()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
285
|
+
if (heightBehavior != EditorHeightBehavior.AUTO_GROW) {
|
|
286
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
val childWidthSpec = getChildMeasureSpec(
|
|
291
|
+
widthMeasureSpec,
|
|
292
|
+
paddingLeft + paddingRight,
|
|
293
|
+
editorViewport.layoutParams.width
|
|
294
|
+
)
|
|
295
|
+
val childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
|
296
|
+
editorViewport.measure(childWidthSpec, childHeightSpec)
|
|
297
|
+
|
|
298
|
+
val measuredWidth = resolveSize(
|
|
299
|
+
editorViewport.measuredWidth + paddingLeft + paddingRight,
|
|
300
|
+
widthMeasureSpec
|
|
301
|
+
)
|
|
302
|
+
val desiredHeight = editorViewport.measuredHeight + paddingTop + paddingBottom
|
|
303
|
+
val measuredHeight = when (MeasureSpec.getMode(heightMeasureSpec)) {
|
|
304
|
+
MeasureSpec.AT_MOST -> desiredHeight.coerceAtMost(MeasureSpec.getSize(heightMeasureSpec))
|
|
305
|
+
else -> desiredHeight
|
|
306
|
+
}
|
|
307
|
+
setMeasuredDimension(measuredWidth, measuredHeight)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private fun updateScrollContainerAppearance() {
|
|
311
|
+
val cornerRadiusPx = (theme?.borderRadius ?: 0f) * resources.displayMetrics.density
|
|
312
|
+
val backgroundColor = theme?.backgroundColor ?: baseBackgroundColor
|
|
313
|
+
editorViewport.background = GradientDrawable().apply {
|
|
314
|
+
cornerRadius = cornerRadiusPx
|
|
315
|
+
setColor(backgroundColor)
|
|
316
|
+
}
|
|
317
|
+
editorViewport.clipToOutline = cornerRadiusPx > 0f
|
|
318
|
+
editorScrollView.setBackgroundColor(Color.TRANSPARENT)
|
|
319
|
+
appliedCornerRadiusPx = cornerRadiusPx
|
|
320
|
+
appliedBackgroundColorForTesting = backgroundColor
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private fun updateScrollContainerInsets() {
|
|
324
|
+
if (heightBehavior != EditorHeightBehavior.FIXED) {
|
|
325
|
+
editorScrollView.setPadding(0, 0, 0, 0)
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
val density = resources.displayMetrics.density
|
|
330
|
+
val topInset = ((theme?.contentInsets?.top ?: 0f) * density).toInt()
|
|
331
|
+
val bottomInset = ((theme?.contentInsets?.bottom ?: 0f) * density).toInt()
|
|
332
|
+
editorScrollView.setPadding(0, topInset, 0, bottomInset + viewportBottomInsetPx)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private fun createContainerLayoutParams(): LayoutParams =
|
|
336
|
+
if (heightBehavior == EditorHeightBehavior.AUTO_GROW) {
|
|
337
|
+
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
338
|
+
} else {
|
|
339
|
+
LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private fun createEditorLayoutParams(): FrameLayout.LayoutParams =
|
|
343
|
+
FrameLayout.LayoutParams(
|
|
344
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
345
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
internal fun selectedImageGeometry(): EditorEditText.SelectedImageGeometry? {
|
|
349
|
+
val geometry = editorEditText.selectedImageGeometry() ?: return null
|
|
350
|
+
return EditorEditText.SelectedImageGeometry(
|
|
351
|
+
docPos = geometry.docPos,
|
|
352
|
+
rect = RectF(
|
|
353
|
+
editorViewport.left + editorScrollView.left + editorEditText.left + geometry.rect.left,
|
|
354
|
+
editorViewport.top + editorScrollView.top + editorEditText.top + geometry.rect.top - editorScrollView.scrollY,
|
|
355
|
+
editorViewport.left + editorScrollView.left + editorEditText.left + geometry.rect.right,
|
|
356
|
+
editorViewport.top + editorScrollView.top + editorEditText.top + geometry.rect.bottom - editorScrollView.scrollY
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
internal fun caretRect(): RectF? {
|
|
362
|
+
val rect = editorEditText.caretRect() ?: return null
|
|
363
|
+
return RectF(
|
|
364
|
+
editorViewport.left + editorScrollView.left + editorEditText.left + rect.left,
|
|
365
|
+
editorViewport.top + editorScrollView.top + editorEditText.top + rect.top - editorScrollView.scrollY,
|
|
366
|
+
editorViewport.left + editorScrollView.left + editorEditText.left + rect.right,
|
|
367
|
+
editorViewport.top + editorScrollView.top + editorEditText.top + rect.bottom - editorScrollView.scrollY
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
internal fun maximumImageWidthPx(): Float {
|
|
372
|
+
val availableWidth =
|
|
373
|
+
maxOf(editorEditText.width, editorEditText.measuredWidth) -
|
|
374
|
+
editorEditText.compoundPaddingLeft -
|
|
375
|
+
editorEditText.compoundPaddingRight
|
|
376
|
+
return availableWidth.coerceAtLeast(48).toFloat()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
internal fun clampImageSize(
|
|
380
|
+
widthPx: Float,
|
|
381
|
+
heightPx: Float,
|
|
382
|
+
maximumWidthPx: Float = maximumImageWidthPx()
|
|
383
|
+
): Pair<Float, Float> {
|
|
384
|
+
val aspectRatio = maxOf(widthPx / maxOf(heightPx, 1f), 0.1f)
|
|
385
|
+
val clampedWidth = minOf(maxOf(48f, maximumWidthPx), maxOf(48f, widthPx))
|
|
386
|
+
val clampedHeight = maxOf(48f, clampedWidth / aspectRatio)
|
|
387
|
+
return clampedWidth to clampedHeight
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
internal fun resizeImage(docPos: Int, widthPx: Float, heightPx: Float) {
|
|
391
|
+
val (clampedWidth, clampedHeight) = clampImageSize(widthPx, heightPx)
|
|
392
|
+
editorEditText.resizeImageAtDocPos(docPos, clampedWidth, clampedHeight)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private fun refreshOverlays() {
|
|
396
|
+
remoteSelectionOverlayView.refreshGeometry()
|
|
397
|
+
imageResizeOverlayView.refresh()
|
|
398
|
+
}
|
|
399
|
+
}
|