Skip to content

Commit 48d0368

Browse files
authored
Horizontal scroll feature (#53)
* horizontal fix * horizontal fix
1 parent 198a1e5 commit 48d0368

7 files changed

Lines changed: 215 additions & 4 deletions

File tree

app/src/main/java/com/alexdremov/notate/CanvasActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,11 @@ class CanvasActivity : AppCompatActivity() {
569569
binding.toolbarContainer.isDragEnabled = !isEdit
570570
}
571571
}
572+
launch {
573+
viewModel.isFixedPageCenterHorizontal.collect { enabled ->
574+
binding.canvasView.setFixedPageCenterHorizontal(enabled)
575+
}
576+
}
572577

573578
// Session Observation
574579
launch {

app/src/main/java/com/alexdremov/notate/data/PreferencesManager.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ object PreferencesManager {
5858
private const val KEY_MIN_LOG_LEVEL = "min_log_level_to_show"
5959
private const val KEY_PDF_EXPORT_SCALE = "pdf_export_scale"
6060
private const val KEY_SYNC_PDF_TYPE = "sync_pdf_type"
61+
private const val KEY_FIXED_PAGE_CENTER_HORIZONTAL = "fixed_page_center_horizontal"
6162

6263
// Debug Preferences
6364
private const val KEY_DEBUG_USE_SIMPLE_RENDERER = "debug_use_simple_renderer"
@@ -107,6 +108,15 @@ object PreferencesManager {
107108
getPrefs(context).edit().putString(KEY_SYNC_PDF_TYPE, type).apply()
108109
}
109110

111+
fun isFixedPageCenterHorizontalEnabled(context: Context): Boolean = getPrefs(context).getBoolean(KEY_FIXED_PAGE_CENTER_HORIZONTAL, true)
112+
113+
fun setFixedPageCenterHorizontalEnabled(
114+
context: Context,
115+
enabled: Boolean,
116+
) {
117+
getPrefs(context).edit().putBoolean(KEY_FIXED_PAGE_CENTER_HORIZONTAL, enabled).apply()
118+
}
119+
110120
fun getMinLogLevel(context: Context): Int = getPrefs(context).getInt(KEY_MIN_LOG_LEVEL, 4) // Default to NONE (4)
111121

112122
fun setMinLogLevel(

app/src/main/java/com/alexdremov/notate/ui/OnyxCanvasView.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ class OnyxCanvasView
221221
}
222222
}
223223

224+
override fun onSizeChanged(
225+
w: Int,
226+
h: Int,
227+
oldw: Int,
228+
oldh: Int,
229+
) {
230+
super.onSizeChanged(w, h, oldw, oldh)
231+
viewportInteractor.setViewWidth(w)
232+
}
233+
224234
private fun setupGestureDetectors() {
225235
gestureDetector =
226236
android.view.GestureDetector(
@@ -688,12 +698,17 @@ class OnyxCanvasView
688698
matrix.postScale(data.zoomLevel, data.zoomLevel)
689699
matrix.postTranslate(data.offsetX, data.offsetY)
690700
viewportInteractor.setScale(data.zoomLevel)
701+
viewportInteractor.setCanvasWidth(data.pageWidth)
691702

692703
canvasRenderer.updateLayoutStrategy()
693704
canvasRenderer.clearTiles()
694705
drawContent()
695706
}
696707

708+
fun setFixedPageCenterHorizontal(enabled: Boolean) {
709+
viewportInteractor.setFixedPageCenterHorizontal(enabled)
710+
}
711+
697712
fun setTool(tool: PenTool) {
698713
this.currentTool = tool
699714
penInputHandler.setTool(tool)

app/src/main/java/com/alexdremov/notate/ui/SettingsSidebarController.kt

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import android.view.View
66
import android.view.ViewGroup
77
import android.widget.*
88
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
10+
import androidx.compose.foundation.layout.fillMaxWidth
911
import androidx.compose.foundation.layout.padding
10-
import androidx.compose.material3.HorizontalDivider
11-
import androidx.compose.material3.Surface
12+
import androidx.compose.material3.*
1213
import androidx.compose.runtime.collectAsState
1314
import androidx.compose.runtime.getValue
15+
import androidx.compose.ui.Alignment
1416
import androidx.compose.ui.Modifier
1517
import androidx.compose.ui.graphics.Color
1618
import androidx.compose.ui.platform.ComposeView
@@ -70,6 +72,19 @@ class SettingsSidebarController(
7072
showWritingMenu()
7173
}
7274

75+
val docMenuItem = mainMenuView.findViewById<View>(R.id.menu_item_document)
76+
val docDivider = mainMenuView.findViewById<View>(R.id.divider_document)
77+
if (isFixedPageMode()) {
78+
docMenuItem.visibility = View.VISIBLE
79+
docDivider?.visibility = View.VISIBLE
80+
docMenuItem.setOnClickListener {
81+
showDocumentMenu()
82+
}
83+
} else {
84+
docMenuItem.visibility = View.GONE
85+
docDivider?.visibility = View.GONE
86+
}
87+
7388
mainMenuView.findViewById<View>(R.id.menu_item_export).setOnClickListener {
7489
showExportMenu()
7590
}
@@ -83,6 +98,60 @@ class SettingsSidebarController(
8398
}
8499
}
85100

101+
private fun showDocumentMenu() {
102+
contentFrame.removeAllViews()
103+
104+
tvTitle.text = "Document"
105+
btnBack.visibility = View.VISIBLE
106+
107+
val composeView =
108+
ComposeView(context).apply {
109+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
110+
setContent {
111+
NotateTheme {
112+
Surface(color = Color.White) {
113+
Column(
114+
modifier = Modifier.padding(16.dp),
115+
) {
116+
val isFixedPage by viewModel.isFixedPageMode.collectAsState()
117+
val isCentered by viewModel.isFixedPageCenterHorizontal.collectAsState()
118+
119+
androidx.compose.material3.Text(
120+
text = "Scrolling",
121+
style = MaterialTheme.typography.titleSmall,
122+
modifier = Modifier.padding(bottom = 8.dp),
123+
)
124+
125+
Row(
126+
modifier = Modifier.fillMaxWidth(),
127+
verticalAlignment = Alignment.CenterVertically,
128+
) {
129+
Column(modifier = Modifier.weight(1f)) {
130+
androidx.compose.material3.Text(
131+
text = "Force horizontal centering",
132+
style = MaterialTheme.typography.bodyLarge,
133+
)
134+
androidx.compose.material3.Text(
135+
text = "Restricts horizontal scrolling in fixed-page mode",
136+
style = MaterialTheme.typography.bodySmall,
137+
color = Color.Gray,
138+
)
139+
}
140+
androidx.compose.material3.Switch(
141+
checked = isCentered,
142+
onCheckedChange = { viewModel.setFixedPageCenterHorizontal(it) },
143+
enabled = isFixedPage,
144+
)
145+
}
146+
}
147+
}
148+
}
149+
}
150+
}
151+
152+
contentFrame.addView(composeView)
153+
}
154+
86155
private fun showWritingMenu() {
87156
contentFrame.removeAllViews()
88157

app/src/main/java/com/alexdremov/notate/ui/interaction/ViewportInteractor.kt

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class ViewportInteractor(
4141
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
4242
private val POST_STROKE_SETTLING_MS = 150L // Ignore touch right after pen release to prevent jitter
4343

44+
// Document Centering
45+
private var isFixedPageCenterHorizontal = false
46+
private var canvasWidth = 0f
47+
private var viewWidth = 0
48+
4449
// Scale Detector
4550
private val scaleDetector =
4651
ScaleGestureDetector(
@@ -115,7 +120,13 @@ class ViewportInteractor(
115120
filteredDx = filteredDx * SMOOTHING_FACTOR + rawDx * (1 - SMOOTHING_FACTOR)
116121
filteredDy = filteredDy * SMOOTHING_FACTOR + rawDy * (1 - SMOOTHING_FACTOR)
117122

118-
matrix.postTranslate(filteredDx, filteredDy)
123+
val dx = if (isFixedPageCenterHorizontal) 0f else filteredDx
124+
matrix.postTranslate(dx, filteredDy)
125+
126+
if (isFixedPageCenterHorizontal) {
127+
enforceCentering()
128+
}
129+
119130
com.alexdremov.notate.util.PerformanceProfiler.trace("ViewportInteractor.invalidateCallback") {
120131
invalidateCallback()
121132
}
@@ -153,13 +164,33 @@ class ViewportInteractor(
153164
}
154165

155166
matrix.postScale(scaleFactor, scaleFactor, detector.focusX, detector.focusY)
167+
168+
if (isFixedPageCenterHorizontal) {
169+
enforceCentering()
170+
}
171+
156172
com.alexdremov.notate.util.PerformanceProfiler.trace("ViewportInteractor.invalidateCallback") {
157173
invalidateCallback()
158174
}
159175
onScaleChanged()
160176
}
161177
}
162178

179+
private fun enforceCentering() {
180+
if (viewWidth <= 0 || canvasWidth <= 0f) return
181+
182+
// Calculate current translation
183+
val values = FloatArray(9)
184+
matrix.getValues(values)
185+
val currentTx = values[Matrix.MTRANS_X]
186+
187+
// Target TX = (viewWidth - canvasWidth * currentScale) / 2
188+
val targetTx = (viewWidth - canvasWidth * currentScale) / 2f
189+
val deltaX = targetTx - currentTx
190+
191+
matrix.postTranslate(deltaX, 0f)
192+
}
193+
163194
private fun updateFocusPoint(event: MotionEvent) {
164195
lastTouchX = getFocusX(event)
165196
lastTouchY = getFocusY(event)
@@ -209,9 +240,36 @@ class ViewportInteractor(
209240

210241
fun setScale(scale: Float) {
211242
currentScale = scale
243+
if (isFixedPageCenterHorizontal) {
244+
enforceCentering()
245+
}
212246
}
213247

214248
fun isInteracting() = isPanning || isInteracting
215249

216250
fun isBusy() = isPanning || hasPerformedScale
251+
252+
fun setFixedPageCenterHorizontal(enabled: Boolean) {
253+
isFixedPageCenterHorizontal = enabled
254+
if (enabled) {
255+
enforceCentering()
256+
invalidateCallback()
257+
}
258+
}
259+
260+
fun setCanvasWidth(width: Float) {
261+
canvasWidth = width
262+
if (isFixedPageCenterHorizontal) {
263+
enforceCentering()
264+
invalidateCallback()
265+
}
266+
}
267+
268+
fun setViewWidth(width: Int) {
269+
viewWidth = width
270+
if (isFixedPageCenterHorizontal) {
271+
enforceCentering()
272+
invalidateCallback()
273+
}
274+
}
217275
}

app/src/main/java/com/alexdremov/notate/vm/DrawingViewModel.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class DrawingViewModel
6060
private val _isFixedPageMode = MutableStateFlow(false)
6161
val isFixedPageMode: StateFlow<Boolean> = _isFixedPageMode.asStateFlow()
6262

63+
private val _isFixedPageCenterHorizontal = MutableStateFlow(true)
64+
val isFixedPageCenterHorizontal: StateFlow<Boolean> = _isFixedPageCenterHorizontal.asStateFlow()
65+
6366
private val _isCollapsibleToolbar = MutableStateFlow(false)
6467
val isCollapsibleToolbar: StateFlow<Boolean> = _isCollapsibleToolbar.asStateFlow()
6568

@@ -79,6 +82,7 @@ class DrawingViewModel
7982
loadTools()
8083
_isCollapsibleToolbar.value = PreferencesManager.isCollapsibleToolbarEnabled(getApplication())
8184
_toolbarCollapseTimeout.value = PreferencesManager.getToolbarCollapseTimeout(getApplication())
85+
_isFixedPageCenterHorizontal.value = PreferencesManager.isFixedPageCenterHorizontalEnabled(getApplication())
8286
}
8387

8488
suspend fun loadCanvasSession(path: String) {
@@ -213,6 +217,14 @@ class DrawingViewModel
213217

214218
fun setFixedPageMode(isFixed: Boolean) {
215219
_isFixedPageMode.value = isFixed
220+
if (!isFixed) {
221+
_isFixedPageCenterHorizontal.value = false
222+
}
223+
}
224+
225+
fun setFixedPageCenterHorizontal(enabled: Boolean) {
226+
_isFixedPageCenterHorizontal.value = enabled
227+
PreferencesManager.setFixedPageCenterHorizontalEnabled(getApplication(), enabled)
216228
}
217229

218230
fun setEditMode(enabled: Boolean) {

app/src/main/res/layout/sidebar_main_menu.xml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,49 @@
135135
android:background="#000000"
136136
android:layout_marginStart="56dp" />
137137

138-
<!-- Export Item -->
138+
<!-- Document Item -->
139+
<LinearLayout
140+
android:id="@+id/menu_item_document"
141+
android:layout_width="match_parent"
142+
android:layout_height="56dp"
143+
android:orientation="horizontal"
144+
android:gravity="center_vertical"
145+
android:background="?attr/selectableItemBackground"
146+
android:clickable="true"
147+
android:focusable="true"
148+
android:paddingStart="16dp"
149+
android:paddingEnd="16dp">
150+
151+
<ImageView
152+
android:layout_width="24dp"
153+
android:layout_height="24dp"
154+
android:src="@drawable/ic_link_file"
155+
app:tint="#000000" />
156+
157+
<TextView
158+
android:layout_width="0dp"
159+
android:layout_height="wrap_content"
160+
android:layout_weight="1"
161+
android:text="Document"
162+
android:textSize="@dimen/text_size_body"
163+
android:textStyle="bold"
164+
android:textColor="#000000"
165+
android:layout_marginStart="16dp" />
166+
167+
<ImageView
168+
android:layout_width="24dp"
169+
android:layout_height="24dp"
170+
android:src="@drawable/ic_chevron_right"
171+
app:tint="#000000" />
172+
173+
</LinearLayout>
174+
175+
<View
176+
android:id="@+id/divider_document"
177+
android:layout_width="match_parent"
178+
android:layout_height="@dimen/ui_stroke_standard"
179+
android:background="#000000"
180+
android:layout_marginStart="56dp" />
139181
<LinearLayout
140182
android:id="@+id/menu_item_export"
141183
android:layout_width="match_parent"

0 commit comments

Comments
 (0)