diff --git a/components/parser/ktfile/build.gradle.kts b/components/parser/ktfile/build.gradle.kts index 54815732..38fd6b70 100644 --- a/components/parser/ktfile/build.gradle.kts +++ b/components/parser/ktfile/build.gradle.kts @@ -12,9 +12,9 @@ dependencies { implementation(projects.components.ir) implementation(projects.components.psi.imagevector) + implementation(compose.material3) implementation(compose.ui) - testImplementation(compose.material3) testImplementation(libs.bundles.test) testRuntimeOnly(libs.junit.launcher) // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4 diff --git a/components/parser/ktfile/src/main/kotlin/io/github/composegears/valkyrie/parser/ktfile/util/IrToImageVector.kt b/components/parser/ktfile/src/main/kotlin/io/github/composegears/valkyrie/parser/ktfile/util/IrToImageVector.kt index 4d1b4e98..aeaee330 100644 --- a/components/parser/ktfile/src/main/kotlin/io/github/composegears/valkyrie/parser/ktfile/util/IrToImageVector.kt +++ b/components/parser/ktfile/src/main/kotlin/io/github/composegears/valkyrie/parser/ktfile/util/IrToImageVector.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.group import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import io.github.composegears.valkyrie.ir.IrFill import io.github.composegears.valkyrie.ir.IrImageVector @@ -39,11 +40,14 @@ import io.github.composegears.valkyrie.ir.IrStrokeLineJoin import io.github.composegears.valkyrie.ir.IrVectorNode.IrGroup import io.github.composegears.valkyrie.ir.IrVectorNode.IrPath -internal fun IrImageVector.toComposeImageVector(): ImageVector { +fun IrImageVector.toComposeImageVector( + width: Dp = Dp.Unspecified, + height: Dp = Dp.Unspecified, +): ImageVector { return ImageVector.Builder( name = name, - defaultWidth = defaultWidth.dp, - defaultHeight = defaultHeight.dp, + defaultWidth = if (width == Dp.Unspecified) defaultWidth.dp else width, + defaultHeight = if (height == Dp.Unspecified) defaultHeight.dp else height, viewportWidth = viewportWidth, viewportHeight = viewportHeight, autoMirror = autoMirror, diff --git a/idea-plugin/build.gradle.kts b/idea-plugin/build.gradle.kts index 82d43488..b9bc419f 100644 --- a/idea-plugin/build.gradle.kts +++ b/idea-plugin/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(projects.components.parser.ktfile) implementation(projects.components.parser.svgxml) implementation(projects.components.psi.iconpack) + implementation(projects.components.psi.imagevector) compileOnly(compose.desktop.currentOs) implementation(compose.desktop.common) diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/TextEditorWithImageVectorPreview.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/TextEditorWithImageVectorPreview.kt index 59827b77..e63e06e2 100644 --- a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/TextEditorWithImageVectorPreview.kt +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/TextEditorWithImageVectorPreview.kt @@ -1,16 +1,6 @@ package io.github.composegears.valkyrie.editor -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel -import androidx.compose.ui.unit.dp import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditorState import com.intellij.openapi.fileEditor.TextEditor @@ -19,16 +9,11 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiFileFactory import com.intellij.util.ui.JBUI -import io.github.composegears.valkyrie.extensions.safeAs -import io.github.composegears.valkyrie.parser.ktfile.KtFileToImageVectorParser -import io.github.composegears.valkyrie.ui.foundation.PixelGrid +import io.github.composegears.valkyrie.editor.ui.ImageVectorPreviewPanel import io.github.composegears.valkyrie.ui.foundation.theme.ValkyrieTheme import java.awt.Dimension import javax.swing.JComponent -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.psi.KtFile class TextEditorWithImageVectorPreview( project: Project, @@ -63,34 +48,7 @@ private class ImageVectorPreviewEditor( private val composePanel = ComposePanel().apply { setContent { ValkyrieTheme(project, this) { - Surface(modifier = Modifier.fillMaxSize()) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - val imageVector = remember(file) { - val ktFile = file.toKtFile(project) - if (ktFile == null) { - null - } else { - KtFileToImageVectorParser.parse(ktFile) - } - } - - if (imageVector == null) { - Text("Failed to parse to ImageVector, please submit issue") - } else { - Box(modifier = Modifier.size(200.dp)) { - PixelGrid( - modifier = Modifier.matchParentSize(), - gridSize = 2.dp, - ) - Image( - modifier = Modifier.size(200.dp), - imageVector = imageVector, - contentDescription = null, - ) - } - } - } - } + ImageVectorPreviewPanel(file) } } preferredSize = JBUI.size(Dimension(800, 800)) @@ -118,11 +76,3 @@ private class ImageVectorPreviewEditor( override fun dispose() {} } - -private fun VirtualFile.toKtFile(project: Project): KtFile? { - val fileContent = contentsToByteArray().toString(Charsets.UTF_8) - - return PsiFileFactory.getInstance(project) - .createFileFromText(name, KotlinFileType.INSTANCE, fileContent) - .safeAs() -} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/VirtualFile.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/VirtualFile.kt new file mode 100644 index 00000000..22e198fb --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/VirtualFile.kt @@ -0,0 +1,16 @@ +package io.github.composegears.valkyrie.editor + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFileFactory +import io.github.composegears.valkyrie.extensions.safeAs +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.psi.KtFile + +internal fun VirtualFile.toKtFile(project: Project): KtFile? { + val fileContent = contentsToByteArray().toString(Charsets.UTF_8) + + return PsiFileFactory.getInstance(project) + .createFileFromText(name, KotlinFileType.INSTANCE, fileContent) + .safeAs() +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/ImageVectorPreviewPanel.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/ImageVectorPreviewPanel.kt new file mode 100644 index 00000000..3fe120ce --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/ImageVectorPreviewPanel.kt @@ -0,0 +1,146 @@ +@file:Suppress("NAME_SHADOWING") + +package io.github.composegears.valkyrie.editor.ui + +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.min +import androidx.compose.ui.unit.times +import com.intellij.openapi.application.readAction +import com.intellij.openapi.vfs.VirtualFile +import io.github.composegears.valkyrie.editor.toKtFile +import io.github.composegears.valkyrie.editor.ui.error.PreviewParsingError +import io.github.composegears.valkyrie.editor.ui.previewer.ImageVectorPreview +import io.github.composegears.valkyrie.editor.ui.previewer.rememberZoomState +import io.github.composegears.valkyrie.ir.IrImageVector +import io.github.composegears.valkyrie.parser.ktfile.util.toComposeImageVector +import io.github.composegears.valkyrie.psi.imagevector.ImageVectorPsiParser +import io.github.composegears.valkyrie.ui.foundation.rememberMutableState +import io.github.composegears.valkyrie.ui.foundation.theme.LocalProject +import kotlin.math.min +import kotlinx.coroutines.launch +import org.jetbrains.kotlin.psi.KtFile + +sealed interface PanelState { + data object Initial : PanelState + data object Error : PanelState + data class Success(val imageVector: ImageVector) : PanelState +} + +@Composable +fun ImageVectorPreviewPanel( + file: VirtualFile, + modifier: Modifier = Modifier, +) { + val density = LocalDensity.current + val project = LocalProject.current + + val scope = rememberCoroutineScope() + val zoomState = rememberZoomState() + + var panelState by rememberMutableState { PanelState.Initial } + + var initialViewportWidth by rememberMutableState { Dp.Unspecified } + var initialViewportHeight by rememberMutableState { Dp.Unspecified } + + val ktFile by produceState(null) { + value = readAction { + file.toKtFile(project) + } + } + var irImageVector by rememberMutableState { null } + + LaunchedEffect(ktFile) { + val ktFile = ktFile ?: return@LaunchedEffect + + readAction { + irImageVector = ImageVectorPsiParser.parseToIrImageVector(ktFile)?.also { + initialViewportWidth = it.defaultWidth.dp + initialViewportHeight = it.defaultHeight.dp + + val maxPreviewSize = zoomState.maxPreviewSize + val initialScale = maxPreviewSize / (3 * min(initialViewportWidth, initialViewportHeight)) + + launch { + zoomState.setScale(initialScale) + } + } + } + + if (irImageVector == null) { + panelState = PanelState.Error + } + } + + LaunchedEffect(irImageVector, zoomState.scale) { + val irImageVector = irImageVector ?: return@LaunchedEffect + + panelState = PanelState.Success( + imageVector = irImageVector.toComposeImageVector( + width = (initialViewportWidth.value * zoomState.scale).dp, + height = (initialViewportHeight.value * zoomState.scale).dp, + ), + ) + } + + BoxWithConstraints( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + zoomState.maxPreviewSize = min(maxWidth, maxHeight) + + when (val state = panelState) { + is PanelState.Error -> PreviewParsingError() + is PanelState.Success -> ImageVectorPreview( + imageVector = state.imageVector, + defaultWidth = initialViewportWidth.value, + defaultHeight = initialViewportHeight.value, + zoomIn = { + if (zoomState.maxPreviewSize >= min( + state.imageVector.defaultWidth, + state.imageVector.defaultHeight, + ) + ) { + scope.launch { + zoomState.zoomIn() + } + } + }, + zoomOut = { + scope.launch { + zoomState.zoomOut() + } + }, + reset = { + scope.launch { + zoomState.reset() + } + }, + fitToWindow = { + scope.launch { + with(density) { + val scaleFactor = min( + zoomState.maxPreviewSize.toPx() / initialViewportWidth.toPx(), + zoomState.maxPreviewSize.toPx() / initialViewportHeight.toPx(), + ) + zoomState.animateToScale(scaleFactor) + } + } + }, + ) + else -> {} + } + } +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/error/PreviewParsingError.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/error/PreviewParsingError.kt new file mode 100644 index 00000000..169c1b8c --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/error/PreviewParsingError.kt @@ -0,0 +1,85 @@ +package io.github.composegears.valkyrie.editor.ui.error + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.UrlAnnotation +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withAnnotation +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.ui.foundation.ClickableText +import io.github.composegears.valkyrie.ui.foundation.WeightSpacer +import io.github.composegears.valkyrie.ui.foundation.icons.Error +import io.github.composegears.valkyrie.ui.foundation.icons.ValkyrieIcons +import io.github.composegears.valkyrie.ui.foundation.platform.rememberBrowserUtil +import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme + +@Composable +internal fun PreviewParsingError() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + WeightSpacer(weight = 0.3f) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), + ) { + Image( + imageVector = ValkyrieIcons.Error, + contentDescription = null, + ) + ErrorMessage() + } + WeightSpacer(weight = 0.7f) + } +} + +@OptIn(ExperimentalTextApi::class) +@Composable +private fun ErrorMessage() { + val browserUtil = rememberBrowserUtil() + + val underlineColor = MaterialTheme.colorScheme.primary + val annotatedString = buildAnnotatedString { + append("Failed to preview ImageVector, please ") + + withAnnotation(UrlAnnotation("https://github.com/ComposeGears/Valkyrie/issues")) { + append( + AnnotatedString( + "submit issue", + spanStyle = SpanStyle( + color = underlineColor, + textDecoration = TextDecoration.Underline, + ), + ), + ) + } + append(" with reproducer.") + } + ClickableText( + annotatedString = annotatedString, + onClick = browserUtil::open, + ) +} + +@Preview +@Composable +private fun PreviewParsingErrorPreview() = PreviewTheme { + PreviewParsingError() +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/ImageVectorPreview.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/ImageVectorPreview.kt new file mode 100644 index 00000000..8519e050 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/ImageVectorPreview.kt @@ -0,0 +1,111 @@ +package io.github.composegears.valkyrie.editor.ui.previewer + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.ui.foundation.previewbg.BgType.PixelGrid +import io.github.composegears.valkyrie.ui.foundation.previewbg.PreviewBackground +import io.github.composegears.valkyrie.ui.foundation.rememberMutableState +import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme + +@Composable +fun ImageVectorPreview( + imageVector: ImageVector, + defaultWidth: Float, + defaultHeight: Float, + zoomIn: () -> Unit, + zoomOut: () -> Unit, + reset: () -> Unit, + modifier: Modifier = Modifier, + fitToWindow: () -> Unit, +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + var bgType by rememberMutableState { PixelGrid } + + BoxWithConstraints( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Box { + PreviewBackground( + bgType = bgType, + modifier = Modifier.matchParentSize(), + ) + Image( + imageVector = imageVector, + contentDescription = null, + ) + } + } + TopActions( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .align(Alignment.TopStart) + .padding(horizontal = 12.dp, vertical = 8.dp), + defaultWidth = defaultWidth, + defaultHeight = defaultHeight, + onBgTypeChange = { + bgType = it + }, + zoomIn = zoomIn, + zoomOut = zoomOut, + onActualSize = reset, + fitToWindow = fitToWindow, + ) + } +} + +@Preview +@Composable +private fun ImageVectorPreviewPreview() = PreviewTheme { + ImageVectorPreview( + imageVector = ImageVector.Builder( + name = "Outlined.Add", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f, + ).apply { + path(fill = SolidColor(Color(0xFF232F34))) { + moveTo(19f, 13f) + lineTo(13f, 13f) + lineTo(13f, 19f) + lineTo(11f, 19f) + lineTo(11f, 13f) + lineTo(5f, 13f) + lineTo(5f, 11f) + lineTo(11f, 11f) + lineTo(11f, 5f) + lineTo(13f, 5f) + lineTo(13f, 11f) + lineTo(19f, 11f) + lineTo(19f, 13f) + close() + } + }.build(), + defaultWidth = 24f, + defaultHeight = 24f, + zoomIn = {}, + zoomOut = {}, + reset = {}, + fitToWindow = {}, + ) +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/TopActions.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/TopActions.kt new file mode 100644 index 00000000..683f9034 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/TopActions.kt @@ -0,0 +1,111 @@ +package io.github.composegears.valkyrie.editor.ui.previewer + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.ui.foundation.TooltipIconButton +import io.github.composegears.valkyrie.ui.foundation.TooltipImageButton +import io.github.composegears.valkyrie.ui.foundation.disabled +import io.github.composegears.valkyrie.ui.foundation.icons.ActualZoom +import io.github.composegears.valkyrie.ui.foundation.icons.BlackCircle +import io.github.composegears.valkyrie.ui.foundation.icons.Chessboard +import io.github.composegears.valkyrie.ui.foundation.icons.FitContent +import io.github.composegears.valkyrie.ui.foundation.icons.ValkyrieIcons +import io.github.composegears.valkyrie.ui.foundation.icons.WhiteCircle +import io.github.composegears.valkyrie.ui.foundation.icons.ZoomIn +import io.github.composegears.valkyrie.ui.foundation.icons.ZoomOut +import io.github.composegears.valkyrie.ui.foundation.previewbg.BgType +import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme + +@Composable +fun TopActions( + defaultWidth: Float, + defaultHeight: Float, + onBgTypeChange: (BgType) -> Unit, + zoomIn: () -> Unit, + zoomOut: () -> Unit, + onActualSize: () -> Unit, + fitToWindow: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp), + ) { + TooltipIconButton( + tooltipText = "Show pixel grid", + icon = ValkyrieIcons.Chessboard, + onClick = { onBgTypeChange(BgType.PixelGrid) }, + ) + TooltipImageButton( + tooltipText = "Show white background", + icon = ValkyrieIcons.WhiteCircle, + onClick = { onBgTypeChange(BgType.White) }, + ) + TooltipImageButton( + tooltipText = "Show black background", + icon = ValkyrieIcons.BlackCircle, + onClick = { onBgTypeChange(BgType.Black) }, + ) + VerticalDivider( + modifier = Modifier.height(20.dp).padding(horizontal = 2.dp), + color = MaterialTheme.colorScheme.onSurface.disabled(), + ) + TooltipIconButton( + tooltipText = "Zoom In", + icon = ValkyrieIcons.ZoomIn, + onClick = zoomIn, + ) + TooltipIconButton( + tooltipText = "Zoom Out", + icon = ValkyrieIcons.ZoomOut, + onClick = zoomOut, + ) + TooltipIconButton( + tooltipText = "Actual Size", + icon = ValkyrieIcons.ActualZoom, + onClick = onActualSize, + ) + TooltipIconButton( + tooltipText = "Fit Zoom to Window", + icon = ValkyrieIcons.FitContent, + onClick = fitToWindow, + ) + Text( + modifier = Modifier.weight(1f), + text = "${defaultWidth.toInt()}x${defaultHeight.toInt()}", + textAlign = TextAlign.End, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + ) + } +} + +@Preview +@Composable +private fun TopActionsPreview() = PreviewTheme { + TopActions( + defaultWidth = 100f, + defaultHeight = 100f, + onBgTypeChange = {}, + modifier = Modifier.padding(8.dp), + zoomIn = {}, + zoomOut = {}, + onActualSize = {}, + fitToWindow = {}, + ) +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/ZoomState.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/ZoomState.kt new file mode 100644 index 00000000..22e587a1 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/editor/ui/previewer/ZoomState.kt @@ -0,0 +1,39 @@ +package io.github.composegears.valkyrie.editor.ui.previewer + +import androidx.compose.animation.core.Animatable +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.unit.Dp + +@Composable +fun rememberZoomState() = remember { ZoomState() } + +class ZoomState { + var maxPreviewSize by mutableStateOf(Dp.Unspecified) + + private val animatable = Animatable(0f) + val scale by animatable.asState() + + suspend fun animateToScale(targetScale: Float) { + animatable.animateTo(targetScale) + } + + suspend fun setScale(targetScale: Float) { + animatable.snapTo(targetScale) + } + + suspend fun zoomIn() { + animateToScale(scale + 1f) + } + + suspend fun zoomOut() { + if (scale - 1f > 0f) { + animateToScale(scale - 1f) + } + } + + suspend fun reset() = animateToScale(1f) +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/ClickableText.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/ClickableText.kt new file mode 100644 index 00000000..b5bd07d0 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/ClickableText.kt @@ -0,0 +1,73 @@ +package io.github.composegears.valkyrie.ui.foundation + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.UrlAnnotation +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withAnnotation +import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme + +@OptIn(ExperimentalFoundationApi::class, ExperimentalTextApi::class) +@Composable +fun ClickableText( + annotatedString: AnnotatedString, + onClick: (String) -> Unit, + modifier: Modifier = Modifier, +) { + var isOnHoverLink by rememberMutableState { false } + + ClickableText( + modifier = modifier.pointerHoverIcon( + icon = if (isOnHoverLink) PointerIcon.Hand else PointerIcon.Default, + ), + text = annotatedString, + onHover = { index -> + if (index != null) { + isOnHoverLink = annotatedString.getUrlAnnotations(index, index + 1).firstOrNull() != null + } + }, + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurface, + ), + onClick = { offset -> + annotatedString.getUrlAnnotations(offset, offset + 1) + .firstOrNull() + ?.let { urlAnnotation -> + onClick(urlAnnotation.item.url) + } + }, + ) +} + +@OptIn(ExperimentalTextApi::class) +@Preview +@Composable +private fun ClickableTextPreview() = PreviewTheme { + val annotatedString = buildAnnotatedString { + append("Sample text with ") + withAnnotation(UrlAnnotation("https://github.com/")) { + append( + AnnotatedString( + text = "url", + spanStyle = SpanStyle(textDecoration = TextDecoration.Underline), + ), + ) + } + } + ClickableText( + annotatedString = annotatedString, + onClick = {}, + ) +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/TooltipButton.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/TooltipButton.kt new file mode 100644 index 00000000..1f5d73cf --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/TooltipButton.kt @@ -0,0 +1,130 @@ +package io.github.composegears.valkyrie.ui.foundation + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.TooltipArea +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.ui.foundation.icons.BlackCircle +import io.github.composegears.valkyrie.ui.foundation.icons.Chessboard +import io.github.composegears.valkyrie.ui.foundation.icons.ValkyrieIcons +import io.github.composegears.valkyrie.ui.foundation.icons.WhiteCircle +import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun TooltipIconButton( + tooltipText: String, + onClick: () -> Unit, + icon: ImageVector, + modifier: Modifier = Modifier, +) { + TooltipArea( + modifier = modifier, + tooltip = { + Box( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(MaterialTheme.colorScheme.inverseSurface) + .padding(8.dp), + ) { + Text( + text = tooltipText, + color = MaterialTheme.colorScheme.inverseOnSurface, + style = MaterialTheme.typography.bodySmall, + ) + } + }, + content = { + Box( + modifier = Modifier.size(24.dp) + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Icon( + tint = MaterialTheme.colorScheme.onSurface, + imageVector = icon, + contentDescription = null, + ) + } + }, + ) +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun TooltipImageButton( + tooltipText: String, + icon: ImageVector, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TooltipArea( + modifier = modifier, + tooltip = { + Box( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(MaterialTheme.colorScheme.inverseSurface) + .padding(8.dp), + ) { + Text( + text = tooltipText, + color = MaterialTheme.colorScheme.inverseOnSurface, + style = MaterialTheme.typography.bodySmall, + ) + } + }, + content = { + Box( + modifier = Modifier.size(24.dp) + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Image( + imageVector = icon, + contentDescription = null, + ) + } + }, + ) +} + +@Preview +@Composable +private fun TooltipButtonPreview() = PreviewTheme { + Row { + TooltipIconButton( + tooltipText = "Show pixel grid", + onClick = {}, + icon = ValkyrieIcons.Chessboard, + ) + TooltipImageButton( + tooltipText = "Show white background", + icon = ValkyrieIcons.WhiteCircle, + onClick = {}, + ) + TooltipImageButton( + tooltipText = "Show black background", + icon = ValkyrieIcons.BlackCircle, + onClick = {}, + ) + } +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ActualZoom.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ActualZoom.kt new file mode 100644 index 00000000..e8e75a41 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ActualZoom.kt @@ -0,0 +1,67 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.ActualZoom: ImageVector + get() { + if (_ActualZoom != null) { + return _ActualZoom!! + } + _ActualZoom = ImageVector.Builder( + name = "ActualZoom", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(3.346f, 12f) + verticalLineTo(6.096f) + horizontalLineTo(1.372f) + verticalLineTo(4.872f) + horizontalLineTo(2.488f) + curveTo(2.732f, 4.872f, 2.946f, 4.82f, 3.13f, 4.716f) + curveTo(3.318f, 4.612f, 3.462f, 4.464f, 3.562f, 4.272f) + curveTo(3.662f, 4.08f, 3.712f, 3.856f, 3.712f, 3.6f) + horizontalLineTo(3.718f) + horizontalLineTo(4.816f) + verticalLineTo(12f) + horizontalLineTo(3.346f) + close() + moveTo(7.069f, 10.398f) + horizontalLineTo(8.671f) + verticalLineTo(12f) + horizontalLineTo(7.069f) + verticalLineTo(10.398f) + close() + moveTo(7.069f, 5.658f) + horizontalLineTo(8.671f) + verticalLineTo(7.26f) + horizontalLineTo(7.069f) + verticalLineTo(5.658f) + close() + moveTo(11.962f, 12f) + verticalLineTo(6.096f) + horizontalLineTo(9.988f) + verticalLineTo(4.872f) + horizontalLineTo(11.104f) + curveTo(11.348f, 4.872f, 11.562f, 4.82f, 11.746f, 4.716f) + curveTo(11.934f, 4.612f, 12.078f, 4.464f, 12.178f, 4.272f) + curveTo(12.278f, 4.08f, 12.328f, 3.856f, 12.328f, 3.6f) + horizontalLineTo(12.334f) + horizontalLineTo(13.432f) + verticalLineTo(12f) + horizontalLineTo(11.962f) + close() + } + }.build() + + return _ActualZoom!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _ActualZoom: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/BlackCircle.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/BlackCircle.kt new file mode 100644 index 00000000..bc523e0a --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/BlackCircle.kt @@ -0,0 +1,43 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.BlackCircle: ImageVector + get() { + if (_BlackCircle != null) { + return _BlackCircle!! + } + _BlackCircle = ImageVector.Builder( + name = "BlackCircle", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = SolidColor(Color(0xFFCED0D6)), + strokeLineWidth = 1f, + ) { + moveTo(8f, 2.5f) + lineTo(8f, 2.5f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 13.5f, 8f) + lineTo(13.5f, 8f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 13.5f) + lineTo(8f, 13.5f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 2.5f, 8f) + lineTo(2.5f, 8f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 2.5f) + close() + } + }.build() + + return _BlackCircle!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _BlackCircle: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/Chessboard.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/Chessboard.kt new file mode 100644 index 00000000..9a6525e7 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/Chessboard.kt @@ -0,0 +1,77 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.Chessboard: ImageVector + get() { + if (_Chessboard != null) { + return _Chessboard!! + } + _Chessboard = ImageVector.Builder( + name = "Chessboard", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(2.5f, 2.5f) + horizontalLineToRelative(3f) + verticalLineToRelative(3f) + horizontalLineToRelative(-3f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(2.5f, 10.5f) + horizontalLineToRelative(3f) + verticalLineToRelative(3f) + horizontalLineToRelative(-3f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(6.5f, 6.5f) + horizontalLineToRelative(3f) + verticalLineToRelative(3f) + horizontalLineToRelative(-3f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(10.5f, 2.5f) + horizontalLineToRelative(3f) + verticalLineToRelative(3f) + horizontalLineToRelative(-3f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(10.5f, 10.5f) + horizontalLineToRelative(3f) + verticalLineToRelative(3f) + horizontalLineToRelative(-3f) + close() + } + }.build() + + return _Chessboard!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _Chessboard: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/Error.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/Error.kt new file mode 100644 index 00000000..211fc973 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/Error.kt @@ -0,0 +1,51 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.Error: ImageVector + get() { + if (_Error != null) { + return _Error!! + } + _Error = ImageVector.Builder( + name = "Error", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path(fill = SolidColor(Color(0xFFE55765))) { + moveTo(8f, 8f) + moveToRelative(-7f, 0f) + arcToRelative(7f, 7f, 0f, isMoreThanHalf = true, isPositiveArc = true, 14f, 0f) + arcToRelative(7f, 7f, 0f, isMoreThanHalf = true, isPositiveArc = true, -14f, 0f) + } + path(fill = SolidColor(Color(0xFFFFFFFF))) { + moveTo(9f, 5f) + curveTo(9f, 4.448f, 8.552f, 4f, 8f, 4f) + curveTo(7.448f, 4f, 7f, 4.448f, 7f, 5f) + verticalLineTo(7.5f) + curveTo(7f, 8.052f, 7.448f, 8.5f, 8f, 8.5f) + curveTo(8.552f, 8.5f, 9f, 8.052f, 9f, 7.5f) + lineTo(9f, 5f) + close() + } + path(fill = SolidColor(Color(0xFFFFFFFF))) { + moveTo(8f, 12f) + curveTo(8.552f, 12f, 9f, 11.552f, 9f, 11f) + curveTo(9f, 10.448f, 8.552f, 10f, 8f, 10f) + curveTo(7.448f, 10f, 7f, 10.448f, 7f, 11f) + curveTo(7f, 11.552f, 7.448f, 12f, 8f, 12f) + close() + } + }.build() + + return _Error!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _Error: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/FitContent.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/FitContent.kt new file mode 100644 index 00000000..c7600852 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/FitContent.kt @@ -0,0 +1,90 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.FitContent: ImageVector + get() { + if (_FitContent != null) { + return _FitContent!! + } + _FitContent = ImageVector.Builder( + name = "FitContent", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(11.5f, 7f) + lineTo(11.5f, 7f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 7.5f) + lineTo(12f, 10.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11.5f, 11f) + lineTo(11.5f, 11f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11f, 10.5f) + lineTo(11f, 7.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11.5f, 7f) + close() + } + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(12f, 10.5f) + lineTo(12f, 10.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11.5f, 11f) + lineTo(8.5f, 11f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 10.5f) + lineTo(8f, 10.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.5f, 10f) + lineTo(11.5f, 10f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 10.5f) + close() + } + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(4.5f, 5f) + lineTo(4.5f, 5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5f, 5.5f) + lineTo(5f, 8.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4.5f, 9f) + lineTo(4.5f, 9f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 8.5f) + lineTo(4f, 5.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4.5f, 5f) + close() + } + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(8f, 5.5f) + lineTo(8f, 5.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 7.5f, 6f) + lineTo(4.5f, 6f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 5.5f) + lineTo(4f, 5.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4.5f, 5f) + lineTo(7.5f, 5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 5.5f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(3f, 2.5f) + lineTo(13f, 2.5f) + arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 14.5f, 4f) + lineTo(14.5f, 12f) + arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 13f, 13.5f) + lineTo(3f, 13.5f) + arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 1.5f, 12f) + lineTo(1.5f, 4f) + arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 3f, 2.5f) + close() + } + }.build() + + return _FitContent!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _FitContent: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/WhiteCircle.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/WhiteCircle.kt new file mode 100644 index 00000000..3d28eb63 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/WhiteCircle.kt @@ -0,0 +1,43 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.WhiteCircle: ImageVector + get() { + if (_WhiteCircle != null) { + return _WhiteCircle!! + } + _WhiteCircle = ImageVector.Builder( + name = "WhiteCircle", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path( + fill = SolidColor(Color(0xFFEBECF0)), + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(8f, 2.5f) + lineTo(8f, 2.5f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 13.5f, 8f) + lineTo(13.5f, 8f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 13.5f) + lineTo(8f, 13.5f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 2.5f, 8f) + lineTo(2.5f, 8f) + arcTo(5.5f, 5.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 2.5f) + close() + } + }.build() + + return _WhiteCircle!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _WhiteCircle: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ZoomIn.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ZoomIn.kt new file mode 100644 index 00000000..8a181475 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ZoomIn.kt @@ -0,0 +1,60 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.ZoomIn: ImageVector + get() { + if (_ZoomIn != null) { + return _ZoomIn!! + } + _ZoomIn = ImageVector.Builder( + name = "ZoomIn", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(8f, 4f) + lineTo(8f, 4f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.5f, 4.5f) + lineTo(8.5f, 11.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 12f) + lineTo(8f, 12f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 7.5f, 11.5f) + lineTo(7.5f, 4.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 4f) + close() + } + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(12f, 8f) + lineTo(12f, 8f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11.5f, 8.5f) + lineTo(4.5f, 8.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 8f) + lineTo(4f, 8f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4.5f, 7.5f) + lineTo(11.5f, 7.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 8f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(8f, 8f) + moveToRelative(-6.5f, 0f) + arcToRelative(6.5f, 6.5f, 0f, isMoreThanHalf = true, isPositiveArc = true, 13f, 0f) + arcToRelative(6.5f, 6.5f, 0f, isMoreThanHalf = true, isPositiveArc = true, -13f, 0f) + } + }.build() + + return _ZoomIn!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _ZoomIn: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ZoomOut.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ZoomOut.kt new file mode 100644 index 00000000..7c830456 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/icons/ZoomOut.kt @@ -0,0 +1,48 @@ +package io.github.composegears.valkyrie.ui.foundation.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.ZoomOut: ImageVector + get() { + if (_ZoomOut != null) { + return _ZoomOut!! + } + _ZoomOut = ImageVector.Builder( + name = "ZoomOut", + defaultWidth = 16.dp, + defaultHeight = 16.dp, + viewportWidth = 16f, + viewportHeight = 16f, + ).apply { + path(fill = SolidColor(Color(0xFF6C707E))) { + moveTo(12f, 8f) + lineTo(12f, 8f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11.5f, 8.5f) + lineTo(4.5f, 8.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 8f) + lineTo(4f, 8f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4.5f, 7.5f) + lineTo(11.5f, 7.5f) + arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 8f) + close() + } + path( + stroke = SolidColor(Color(0xFF6C707E)), + strokeLineWidth = 1f, + ) { + moveTo(8f, 8f) + moveToRelative(-6.5f, 0f) + arcToRelative(6.5f, 6.5f, 0f, isMoreThanHalf = true, isPositiveArc = true, 13f, 0f) + arcToRelative(6.5f, 6.5f, 0f, isMoreThanHalf = true, isPositiveArc = true, -13f, 0f) + } + }.build() + + return _ZoomOut!! + } + +@Suppress("ObjectPropertyName", "ktlint:standard:backing-property-naming") +private var _ZoomOut: ImageVector? = null diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/platform/BrowserUtil.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/platform/BrowserUtil.kt new file mode 100644 index 00000000..dfdd7ecc --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/platform/BrowserUtil.kt @@ -0,0 +1,25 @@ +package io.github.composegears.valkyrie.ui.foundation.platform + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalInspectionMode + +@Composable +fun rememberBrowserUtil(): BrowserUtil { + if (LocalInspectionMode.current) return NoOpBrowserUtil + + return BrowserUtilImpl +} + +interface BrowserUtil { + fun open(url: String) +} + +private object NoOpBrowserUtil : BrowserUtil { + override fun open(url: String) = Unit +} + +private object BrowserUtilImpl : BrowserUtil { + override fun open(url: String) { + com.intellij.ide.BrowserUtil.browse(url) + } +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/previewbg/BgType.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/previewbg/BgType.kt new file mode 100644 index 00000000..509e291a --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/previewbg/BgType.kt @@ -0,0 +1,14 @@ +package io.github.composegears.valkyrie.ui.foundation.previewbg + +enum class BgType { + Black, + White, + PixelGrid, + ; + + fun next(): BgType = when (this) { + Black -> White + White -> PixelGrid + PixelGrid -> Black + } +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/previewbg/PreviewBackground.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/previewbg/PreviewBackground.kt new file mode 100644 index 00000000..2a9d0510 --- /dev/null +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/previewbg/PreviewBackground.kt @@ -0,0 +1,64 @@ +package io.github.composegears.valkyrie.ui.foundation.previewbg + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.ui.foundation.PixelGrid +import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme + +@Composable +fun PreviewBackground( + bgType: BgType, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier) { + when (bgType) { + BgType.Black -> { + Box( + modifier = Modifier + .matchParentSize() + .background(Color.Black), + ) + } + BgType.White -> { + Box( + modifier = Modifier + .matchParentSize() + .background(Color.White), + ) + } + BgType.PixelGrid -> { + PixelGrid( + modifier = Modifier.matchParentSize(), + gridSize = 2.dp, + ) + } + } + } +} + +@Preview +@Composable +private fun PreviewBackgroundPreview() = PreviewTheme { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + PreviewBackground( + modifier = Modifier.size(200.dp), + bgType = BgType.PixelGrid, + ) + PreviewBackground( + modifier = Modifier.size(200.dp), + bgType = BgType.Black, + ) + PreviewBackground( + modifier = Modifier.size(200.dp), + bgType = BgType.White, + ) + } +} diff --git a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/ui/batch/IconPreviewBox.kt b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/ui/batch/IconPreviewBox.kt index 92653d01..3b8500cd 100644 --- a/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/ui/batch/IconPreviewBox.kt +++ b/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/ui/batch/IconPreviewBox.kt @@ -2,7 +2,6 @@ package io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.ba import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size @@ -14,12 +13,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp -import io.github.composegears.valkyrie.ui.foundation.PixelGrid import io.github.composegears.valkyrie.ui.foundation.SizeSpacer +import io.github.composegears.valkyrie.ui.foundation.previewbg.BgType +import io.github.composegears.valkyrie.ui.foundation.previewbg.PreviewBackground import io.github.composegears.valkyrie.ui.foundation.rememberMutableState import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.util.toPainterOrNull @@ -48,28 +47,10 @@ fun IconPreviewBox( }, contentAlignment = Alignment.Center, ) { - when (bgType) { - BgType.Black -> { - Box( - modifier = Modifier - .matchParentSize() - .background(Color.Black), - ) - } - BgType.White -> { - Box( - modifier = Modifier - .matchParentSize() - .background(Color.White), - ) - } - BgType.PixelGrid -> { - PixelGrid( - modifier = Modifier.matchParentSize(), - gridSize = 2.dp, - ) - } - } + PreviewBackground( + bgType = bgType, + modifier = Modifier.matchParentSize(), + ) val iconPainter by produceState(initialValue = null) { value = withContext(Dispatchers.Default) { @@ -88,19 +69,6 @@ fun IconPreviewBox( } } -private enum class BgType { - Black, - White, - PixelGrid, - ; - - fun next(): BgType = when (this) { - Black -> White - White -> PixelGrid - PixelGrid -> Black - } -} - @Preview @Composable private fun IconPreviewBoxPreview() = PreviewTheme {