From 3e137256cb71f2332fa06ed22e5f2f99e991456a Mon Sep 17 00:00:00 2001 From: Wing <44992537+wingio@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:54:15 -0500 Subject: [PATCH] feat: Add gestures to the code viewer - Double tap to show/hide ui - Long press to start selection - Single press while in a selection to select more - Pinch to zoom (min 70%, max 200%) --- .../ui/components/toolbar/SmallToolbar.kt | 5 +- .../ui/screens/explorer/FileViewerScreen.kt | 40 +++- .../explorer/viewers/TextFileViewer.kt | 102 +++++++++- .../materiiapps/gloom/ui/theme/CodeTheme.kt | 4 + .../materiiapps/gloom/ui/utils/DimenUtils.kt | 13 ++ .../materiiapps/gloom/ui/utils/LazyUtil.kt | 19 ++ .../gloom/ui/widgets/code/CodeViewer.kt | 174 +++++++++++++++--- .../commonMain/resources/MR/base/strings.xml | 1 + 8 files changed, 320 insertions(+), 38 deletions(-) create mode 100644 ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/LazyUtil.kt diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/components/toolbar/SmallToolbar.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/components/toolbar/SmallToolbar.kt index e702dcb2..1f05c506 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/components/toolbar/SmallToolbar.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/components/toolbar/SmallToolbar.kt @@ -6,12 +6,14 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.materiiapps.gloom.ui.components.BackButton @Composable @OptIn(ExperimentalMaterial3Api::class) fun SmallToolbar( title: String, + modifier: Modifier = Modifier, actions: @Composable RowScope.() -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior? = null ) { @@ -19,6 +21,7 @@ fun SmallToolbar( title = { Text(text = title) }, navigationIcon = { BackButton() }, actions = actions, - scrollBehavior = scrollBehavior + scrollBehavior = scrollBehavior, + modifier = modifier ) } \ No newline at end of file diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/FileViewerScreen.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/FileViewerScreen.kt index 092e0af6..5a682057 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/FileViewerScreen.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/FileViewerScreen.kt @@ -1,10 +1,12 @@ package com.materiiapps.gloom.ui.screens.explorer +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons @@ -18,9 +20,14 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.getScreenModel import com.materiiapps.gloom.Res @@ -34,6 +41,7 @@ import com.materiiapps.gloom.ui.screens.explorer.viewers.ImageFileViewer import com.materiiapps.gloom.ui.screens.explorer.viewers.MarkdownFileViewer import com.materiiapps.gloom.ui.screens.explorer.viewers.PdfFileViewer import com.materiiapps.gloom.ui.screens.explorer.viewers.TextFileViewer +import com.materiiapps.gloom.ui.utils.thenIf import com.materiiapps.gloom.ui.viewmodels.explorer.FileViewerViewModel import dev.icerock.moko.resources.compose.stringResource import org.koin.androidx.compose.get @@ -56,8 +64,12 @@ class FileViewerScreen( rememberPullRefreshState(viewModel.isLoading, onRefresh = { viewModel.getRepoFile() }) val file = viewModel.file?.gitObject?.onCommit?.file + var topBarHidden by remember { + mutableStateOf(false) + } + Scaffold( - topBar = { Toolbar(scrollBehavior, file) }, + topBar = { Toolbar(scrollBehavior, file, topBarHidden) }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(0, 0, 0, 0) ) { pv -> @@ -76,7 +88,12 @@ class FileViewerScreen( .fillMaxWidth() ) - false -> FileContent(file) + false -> FileContent( + file = file, + onHideToggled = { + topBarHidden = !topBarHidden + } + ) } RefreshIndicator(pullRefreshState, viewModel.isLoading) @@ -85,12 +102,19 @@ class FileViewerScreen( } @Composable - private fun FileContent(file: RepoFile.File?) { + private fun FileContent( + file: RepoFile.File?, + onHideToggled: () -> Unit + ) { when (file?.fileType?.__typename) { "MarkdownFileType" -> MarkdownFileViewer(file.fileType?.onMarkdownFileType!!) "ImageFileType" -> ImageFileViewer(file.fileType?.onImageFileType!!) "PdfFileType" -> PdfFileViewer(file.fileType?.onPdfFileType!!) - "TextFileType" -> TextFileViewer(file.fileType?.onTextFileType!!, file.extension ?: "") + "TextFileType" -> TextFileViewer( + file.fileType?.onTextFileType!!, + file.extension ?: "", + onHideToggled + ) else -> {} } } @@ -117,11 +141,17 @@ class FileViewerScreen( private fun Toolbar( scrollBehavior: TopAppBarScrollBehavior, file: RepoFile.File?, + hidden: Boolean ) { SmallToolbar( title = path.split("/").lastOrNull() ?: "File", actions = { FileActions(file) }, - scrollBehavior = scrollBehavior + scrollBehavior = scrollBehavior, + modifier = Modifier + .animateContentSize() + .thenIf(hidden) { + height(0.dp) + } ) } diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/viewers/TextFileViewer.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/viewers/TextFileViewer.kt index 9b3be1b4..a158c280 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/viewers/TextFileViewer.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/screens/explorer/viewers/TextFileViewer.kt @@ -1,18 +1,112 @@ package com.materiiapps.gloom.ui.screens.explorer.viewers +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.gestures.detectTransformGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SearchOff +import androidx.compose.material3.Icon +import androidx.compose.material3.LargeFloatingActionButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.materiiapps.gloom.Res import com.materiiapps.gloom.gql.fragment.RepoFile +import com.materiiapps.gloom.ui.utils.DimenUtils.multiply import com.materiiapps.gloom.ui.widgets.code.CodeViewer +import dev.icerock.moko.resources.compose.stringResource @Composable fun TextFileViewer( textFile: RepoFile.OnTextFileType, extension: String, + onHideToggled: () -> Unit ) { val content = textFile.contentRaw ?: return - CodeViewer( - code = content, - extension = extension - ) + val lazyListState = rememberLazyListState() + val scrollState = rememberScrollState() + val layoutDirection = LocalLayoutDirection.current + val defaultLineNumberPadding = PaddingValues(horizontal = 8.dp, vertical = 3.dp) + + var scale by remember { mutableFloatStateOf(1f) } + var lineNumberPadding by remember { mutableStateOf(defaultLineNumberPadding) } + var hideFAB by remember { mutableStateOf(false) } + var linesSelected by remember { mutableStateOf(null as IntRange?) } + + Box( + modifier = Modifier.fillMaxSize() + ) { + CodeViewer( + code = content, + extension = extension, + fontSize = 13.sp * scale, + codePadding = 16.dp * scale, + lineNumberPadding = lineNumberPadding, + linesSelected = linesSelected, + onLinesSelected = { + linesSelected = it + }, + onDoubleClick = { // Double tap to hide the ui + onHideToggled() + hideFAB = !hideFAB + }, + modifier = Modifier + .pointerInput(Unit) { + // Pinch to zoom + detectTransformGestures { _, pan, zoom, _ -> + if (scale * zoom in 0.7f..2f) { + scale *= zoom + lineNumberPadding = lineNumberPadding.multiply(zoom, layoutDirection) + } + scrollState.dispatchRawDelta(-(pan.x.toDp().toPx())) + lazyListState.dispatchRawDelta(-(pan.y.toDp().toPx())) + } + } + ) + + AnimatedVisibility( + visible = scale != 1f && !hideFAB, + enter = scaleIn(), + exit = scaleOut(), + modifier = Modifier + .align(Alignment.BottomEnd) + .systemBarsPadding() + .padding(16.dp) + ) { + LargeFloatingActionButton( + shape = RoundedCornerShape(25), + onClick = { + scale = 1f + lineNumberPadding = defaultLineNumberPadding + }, + modifier = Modifier + .size(56.dp) + ) { + Icon( + Icons.Outlined.SearchOff, + contentDescription = stringResource(Res.strings.action_reset_zoom) + ) + } + } + } } \ No newline at end of file diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/theme/CodeTheme.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/theme/CodeTheme.kt index 8158a2cd..4d7ae06e 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/theme/CodeTheme.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/theme/CodeTheme.kt @@ -18,6 +18,7 @@ data class CodeTheme( val background: Color, val linesBackground: Color, val linesContent: Color, + val selectedHighlight: Color, val code: Color, val keyword: Color, val string: Color, @@ -33,11 +34,13 @@ data class CodeTheme( background: Color, linesBackground: Color, linesContent: Color, + selectedHighlight: Color, syntaxTheme: SyntaxTheme ): this( background = background, linesBackground = linesBackground, linesContent = linesContent, + selectedHighlight = selectedHighlight, code = Color(syntaxTheme.code), keyword = Color(syntaxTheme.keyword), string = Color(syntaxTheme.string), @@ -77,6 +80,7 @@ data class CodeTheme( background = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp).copy(alpha = 0.2f), linesBackground = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp), linesContent = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + selectedHighlight = Color(0xFFFF9800), // Orange syntaxTheme = syntaxTheme ) } diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/DimenUtils.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/DimenUtils.kt index 3de1bc8c..a8734ba7 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/DimenUtils.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/DimenUtils.kt @@ -1,17 +1,30 @@ package com.materiiapps.gloom.ui.utils +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection object DimenUtils { val navBarPadding: Dp @Composable get() = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() + fun PaddingValues.multiply(multiplier: Float, layoutDirection: LayoutDirection): PaddingValues { + return PaddingValues( + start = calculateStartPadding(layoutDirection) * multiplier, + end = calculateEndPadding(layoutDirection) * multiplier, + top = calculateTopPadding() * multiplier, + bottom = calculateBottomPadding() * multiplier + ) + } + } @Composable diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/LazyUtil.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/LazyUtil.kt new file mode 100644 index 00000000..716b0573 --- /dev/null +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/utils/LazyUtil.kt @@ -0,0 +1,19 @@ +package com.materiiapps.gloom.ui.utils + +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.ui.geometry.Offset +import kotlin.math.roundToInt + +object LazyUtil { + + fun LazyListState.getItemAtOffset( + offset: Offset, + horizontal: Boolean = false + ): LazyListItemInfo? { + return layoutInfo.visibleItemsInfo.firstOrNull { itemInfo -> + (if (horizontal) offset.x else offset.y).roundToInt() in (itemInfo.offset..itemInfo.offset + itemInfo.size) + } + } + +} \ No newline at end of file diff --git a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/widgets/code/CodeViewer.kt b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/widgets/code/CodeViewer.kt index 9f56d4a1..b8ccdc3f 100644 --- a/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/widgets/code/CodeViewer.kt +++ b/ui/src/commonMain/kotlin/com/materiiapps/gloom/ui/widgets/code/CodeViewer.kt @@ -1,46 +1,62 @@ package com.materiiapps.gloom.ui.widgets.code import androidx.compose.animation.core.animateIntAsState +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.material.Text +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.materiiapps.gloom.ui.components.CodeText import com.materiiapps.gloom.ui.components.scrollbar.ScrollBar import com.materiiapps.gloom.ui.theme.CodeTheme import com.materiiapps.gloom.ui.utils.DimenUtils +import com.materiiapps.gloom.ui.utils.LazyUtil.getItemAtOffset import com.materiiapps.gloom.ui.utils.digits import com.materiiapps.gloom.ui.utils.padLineNumber +import com.materiiapps.gloom.ui.utils.thenIf import com.materiiapps.gloom.ui.utils.toDp import com.materiiapps.gloom.ui.utils.toImmutableList @@ -48,24 +64,30 @@ import com.materiiapps.gloom.ui.utils.toImmutableList fun CodeViewer( code: String, extension: String, + modifier: Modifier = Modifier, + linesSelected: IntRange? = null, + onLinesSelected: ((IntRange?) -> Unit)? = null, + onDoubleClick: (() -> Unit)? = null, + verticalScrollState: LazyListState = rememberLazyListState(), + horizontalScrollState: ScrollState = rememberScrollState(), theme: CodeTheme = CodeTheme.getDefault(), codePadding: Dp = 16.dp, + fontSize: TextUnit = 13.sp, + displayLineNumber: Boolean = true, lineNumberPadding: PaddingValues = PaddingValues(horizontal = 8.dp, vertical = 3.dp) ) { // Layout var maxLineNumWidth by remember { mutableIntStateOf(0) } val maxLineNumWidthAnimated by animateIntAsState(maxLineNumWidth, label = "Line number width") val layoutDirection = LocalLayoutDirection.current - val lineNumberHorizontalPadding = remember(lineNumberPadding, layoutDirection) { + val lineNumberHorizontalPadding = remember(layoutDirection) { lineNumberPadding.calculateStartPadding(layoutDirection) + lineNumberPadding.calculateEndPadding( layoutDirection ) } - // State - val lazyListState = rememberLazyListState() - val scrollState = rememberScrollState() - val canScroll = lazyListState.canScrollForward || lazyListState.canScrollBackward + // Scrolling + val canScroll = verticalScrollState.canScrollForward || verticalScrollState.canScrollBackward // Line processing val lines = remember(code) { @@ -75,69 +97,165 @@ fun CodeViewer( lines.maxOf { it.length } } + // Line click handling + val interactionMap = remember(lines.size) { + mutableStateMapOf() + } + Box( modifier = Modifier.fillMaxWidth() ) { LazyColumn( - state = lazyListState, + state = verticalScrollState, contentPadding = PaddingValues(bottom = if (canScroll) DimenUtils.navBarPadding else 0.dp), modifier = Modifier - .fillMaxWidth() - .horizontalScroll(scrollState) + .fillMaxSize() + .then(modifier) + .thenIf(onLinesSelected != null) { + pointerInput( + verticalScrollState, + onLinesSelected, + linesSelected + ) { + detectTapGestures( + onLongPress = { offset -> + // Only start a selection when there currently isn't one + if (linesSelected == null) { + val itemPressed = verticalScrollState.getItemAtOffset(offset) + val lineNumber = itemPressed?.index?.plus(1) + lineNumber?.let { + onLinesSelected!!(lineNumber..lineNumber) + } + } + }, + onTap = { offset -> + // Only handle when at least one other line is selected, otherwise call onClick + if (linesSelected != null) { + val itemPressed = verticalScrollState.getItemAtOffset(offset) + val lineNumber = itemPressed?.index?.plus(1) + lineNumber?.let { + val newRange = when { + lineNumber > linesSelected.last -> linesSelected.first..lineNumber + lineNumber < linesSelected.first -> lineNumber..linesSelected.last + lineNumber == linesSelected.last -> linesSelected.first until linesSelected.last + lineNumber == linesSelected.first -> linesSelected.first + 1..linesSelected.last + else -> linesSelected + } + + onLinesSelected!!(if (newRange.isEmpty()) null else newRange) + } + } + }, + onDoubleTap = { + onDoubleClick?.invoke() + }, + onPress = { offset -> + // Check if any press related listeners are present + if (onLinesSelected != null && onDoubleClick != null) { + verticalScrollState + .getItemAtOffset(offset) + ?.let { itemPressed -> + // Get the MutableInteractionState for the item + interactionMap[itemPressed.index]?.let { interactionSource -> + val press = PressInteraction.Press( + Offset( + x = offset.x, // Since both the lazy column and line are the same width we don't have to change this + y = itemPressed.size / 2f // Center the interaction + ) + ) + interactionSource.emit(press) + if (tryAwaitRelease()) { + interactionSource.emit( + PressInteraction.Release( + press + ) + ) + } else { + interactionSource.emit( + PressInteraction.Cancel( + press + ) + ) + } + } + } + } + } + ) + } + } + .horizontalScroll(horizontalScrollState) ) { itemsIndexed( items = lines, key = { i, code -> "$i-${code.sumOf { it.code }}" } ) { i, code -> + val interactionSource = + remember { MutableInteractionSource().also { interactionMap[i] = it } } Box( contentAlignment = Alignment.CenterStart, - modifier = Modifier.background(theme.background) + modifier = Modifier + .background(theme.background) + .indication(interactionSource, rememberRipple()) + .thenIf(onLinesSelected != null) { + semantics { + role = Role.Checkbox + if (linesSelected != null) { + selected = i + 1 in linesSelected + } + } + } + .thenIf(linesSelected != null && i + 1 in linesSelected) { + drawWithContent { + drawContent() + drawRect( + color = theme.selectedHighlight.copy(alpha = 0.2f), + size = drawContext.size + ) + } + } ) { + // Highlighted code CodeText( text = code.padEnd(maxLineLength) /* Probably a better way to do this but idk */, extension = extension, theme = theme.syntaxTheme, + fontSize = fontSize, modifier = Modifier + .fillParentMaxWidth() .padding( start = maxLineNumWidthAnimated.toDp() + lineNumberHorizontalPadding /* Account for line number padding */ + codePadding, /* Desired padding */ end = codePadding ) - .fillParentMaxWidth() ) - Row( - modifier = Modifier - .offset { IntOffset(scrollState.value, 0) } - .background(theme.linesBackground) - ) { + + // Line number + if (displayLineNumber) { Text( (i + 1).padLineNumber(maxDigits = lines.size.digits), fontFamily = FontFamily.Monospace, - fontSize = 13.sp, + fontSize = fontSize, textAlign = TextAlign.End, color = theme.linesContent, fontWeight = FontWeight.Bold, modifier = Modifier + .offset { IntOffset(horizontalScrollState.value, 0) } + .background(theme.linesBackground) .padding(lineNumberPadding) .onGloballyPositioned { - if (it.size.width > maxLineNumWidth) maxLineNumWidth = - it.size.width + if (it.size.width != maxLineNumWidth) + maxLineNumWidth = it.size.width } ) - Box( - modifier = Modifier - .background(theme.linesContent) - .width(2.dp) - .fillMaxHeight() - ) } } } } ScrollBar( - scrollState = scrollState, + scrollState = horizontalScrollState, orientation = Orientation.Horizontal, modifier = Modifier .align(Alignment.BottomCenter) diff --git a/ui/src/commonMain/resources/MR/base/strings.xml b/ui/src/commonMain/resources/MR/base/strings.xml index 8f167188..b1e9cfdf 100644 --- a/ui/src/commonMain/resources/MR/base/strings.xml +++ b/ui/src/commonMain/resources/MR/base/strings.xml @@ -31,6 +31,7 @@ Stop editing Sign out of all accounts Try again + Reset zoom Nevermind Cancel