From f3f026b73fef97147a09d4c00be00ef2d12386d8 Mon Sep 17 00:00:00 2001 From: t-regbs Date: Thu, 19 Mar 2026 21:32:42 +0000 Subject: [PATCH 1/4] feat: add more customization options --- tools/idea-plugin/build.gradle.kts | 1 + .../screen/webimport/common/ui/SidePanel.kt | 2 +- .../common/ui/SvgCustomizationPanel.kt | 236 ++++++++++++++++++ .../bootstrap/domain/BootstrapUseCase.kt | 7 +- .../boxicons/domain/BoxIconsUseCase.kt | 7 +- .../standard/common/StandardIconViewModel.kt | 6 +- .../standard/common/StandardImportScreenUI.kt | 14 +- .../common/domain/StandardIconProvider.kt | 13 +- .../common/domain/SvgSizeCustomizer.kt | 80 ++++++ .../standard/common/model/SizeSettings.kt | 18 +- .../model/SvgCustomizationCapabilities.kt | 7 + .../standard/lucide/domain/LucideUseCase.kt | 7 +- .../standard/remix/domain/RemixUseCase.kt | 7 +- .../resources/messages/Valkyrie.properties | 14 ++ 14 files changed, 380 insertions(+), 39 deletions(-) create mode 100644 tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt create mode 100644 tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgCustomizationCapabilities.kt diff --git a/tools/idea-plugin/build.gradle.kts b/tools/idea-plugin/build.gradle.kts index 66525fa6c..5e312ed7f 100644 --- a/tools/idea-plugin/build.gradle.kts +++ b/tools/idea-plugin/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(projects.sdk.intellij.psi.iconpack) implementation(projects.sdk.intellij.psi.imagevector) implementation(projects.sdk.ir.core) + implementation(projects.sdk.parser.common) implementation(projects.sdk.ir.compose) implementation(projects.sdk.ir.util) implementation(projects.sdk.ir.xml) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SidePanel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SidePanel.kt index a8f8ebe66..9ea182c42 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SidePanel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SidePanel.kt @@ -53,7 +53,7 @@ fun SidePanel( ) Box( modifier = Modifier - .widthIn(max = 250.dp) + .widthIn(max = 320.dp) .fillMaxHeight() .clip(RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)) .background(JewelTheme.globalColors.borders.normal), diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt new file mode 100644 index 000000000..1a2e6560c --- /dev/null +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt @@ -0,0 +1,236 @@ +@file:Suppress("DEPRECATION") + +package io.github.composegears.valkyrie.ui.screen.webimport.common.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +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.unit.dp +import com.intellij.ui.ColorChooser +import io.github.composegears.valkyrie.jewel.HorizontalDivider +import io.github.composegears.valkyrie.jewel.settings.DropdownSettingsRow +import io.github.composegears.valkyrie.jewel.tooling.PreviewTheme +import io.github.composegears.valkyrie.sdk.compose.foundation.layout.CenterVerticalRow +import io.github.composegears.valkyrie.sdk.compose.foundation.layout.Spacer +import io.github.composegears.valkyrie.sdk.compose.foundation.layout.WeightSpacer +import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities +import io.github.composegears.valkyrie.util.stringResource +import java.awt.Color as AwtColor +import kotlin.math.roundToInt +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.jetbrains.jewel.foundation.LocalComponent +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.ui.component.CheckboxRow +import org.jetbrains.jewel.ui.component.InfoText +import org.jetbrains.jewel.ui.component.OutlinedButton +import org.jetbrains.jewel.ui.component.Slider +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.styling.LocalGroupHeaderStyle + +private val rotationOptions = listOf(0, 90, 180, 270) +private val presetColors = listOf( + "#F4CC3A", + "#63D3AF", + "#68A9E0", + "#9D86E3", + "#FF9A62", +) + +private fun String.toComposeColor(): Color { + val awtColor = AwtColor.decode(this) + return Color(awtColor.red, awtColor.green, awtColor.blue) +} + +private fun String.toAwtColorOrNull(): AwtColor? = runCatching { AwtColor.decode(this) }.getOrNull() + +private fun AwtColor.toHexColor(): String = String.format("#%02X%02X%02X", red, green, blue) + +@Composable +fun SvgCustomizationPanel( + settings: SizeSettings, + capabilities: SvgCustomizationCapabilities, + onSettingsChange: (SizeSettings) -> Unit, + onClose: () -> Unit, + modifier: Modifier = Modifier, +) { + val noneLabel = stringResource("web.import.font.customize.none") + val customLabel = stringResource("web.import.font.customize.color.custom") + val originalLabel = stringResource("web.import.font.customize.color.original") + val component = LocalComponent.current + + Column(modifier = modifier) { + CustomizationToolbar( + onClose = onClose, + onReset = { onSettingsChange(SizeSettings()) }, + isModified = settings.isModified, + ) + HorizontalDivider(color = LocalGroupHeaderStyle.current.colors.divider) + VerticallyScrollableContainer { + Column( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + if (capabilities.supportsColor) { + CenterVerticalRow(modifier = Modifier.padding(horizontal = 4.dp)) { + Text(text = stringResource("web.import.font.customize.color")) + WeightSpacer() + InfoText(text = settings.color ?: originalLabel) + } + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + ColorSwatch( + label = noneLabel, + color = null, + selected = settings.color == null, + onClick = { onSettingsChange(settings.copy(color = null)) }, + ) + presetColors.forEach { hex -> + ColorSwatch( + label = hex, + color = hex.toComposeColor(), + selected = settings.color == hex, + onClick = { onSettingsChange(settings.copy(color = hex)) }, + ) + } + } + OutlinedButton( + onClick = { + val chosenColor = ColorChooser.chooseColor( + component, + customLabel, + settings.color?.toAwtColorOrNull() ?: AwtColor.decode("#68A9E0"), + false, + ) + if (chosenColor != null) { + onSettingsChange(settings.copy(color = chosenColor.toHexColor())) + } + }, + ) { + Text(text = customLabel) + } + } + + var size by rememberMutableState { settings.size.toFloat() } + LaunchedEffect(settings.size) { + size = settings.size.toFloat() + } + CenterVerticalRow(modifier = Modifier.padding(horizontal = 4.dp)) { + Text(text = stringResource("web.import.font.customize.size")) + WeightSpacer() + InfoText(text = stringResource("web.import.font.customize.px.suffix", size.roundToInt())) + } + Slider( + value = size, + onValueChange = { + size = it + onSettingsChange(settings.copy(size = size.roundToInt())) + }, + valueRange = SizeSettings.MIN_SIZE.toFloat()..SizeSettings.MAX_SIZE.toFloat(), + ) + + if (capabilities.supportsRotation) { + DropdownSettingsRow( + text = stringResource("web.import.font.customize.rotate"), + current = settings.rotation, + items = rotationOptions, + onSelectItem = { onSettingsChange(settings.copy(rotation = it)) }, + comboxModifier = Modifier.width(90.dp), + transform = { + when (it) { + 0 -> noneLabel + else -> "$it${'\u00B0'}" + } + }, + ) + } + + if (capabilities.supportsFlip) { + Text(text = stringResource("web.import.font.customize.flip")) + CheckboxRow( + checked = settings.flipHorizontally, + onCheckedChange = { + onSettingsChange(settings.copy(flipHorizontally = it)) + }, + ) { + Text(text = stringResource("web.import.font.customize.flip.horizontal")) + } + CheckboxRow( + checked = settings.flipVertically, + onCheckedChange = { + onSettingsChange(settings.copy(flipVertically = it)) + }, + ) { + Text(text = stringResource("web.import.font.customize.flip.vertical")) + } + } + } + } + } +} + +@Composable +private fun ColorSwatch( + label: String, + color: Color?, + selected: Boolean, + onClick: () -> Unit, +) { + val borderColor = when { + selected -> JewelTheme.globalColors.text.normal + else -> JewelTheme.globalColors.borders.normal + } + + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(10.dp)) + .background(color ?: JewelTheme.globalColors.panelBackground) + .border(width = 2.dp, color = borderColor, shape = RoundedCornerShape(10.dp)) + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + if (color == null) { + Text(text = label.take(1)) + } + } +} + +@Preview +@Composable +private fun SvgCustomizationPanelPreview() = PreviewTheme(alignment = Alignment.TopEnd) { + var settings by rememberMutableState { SizeSettings(color = "#4F8FBA", rotation = 90) } + + SvgCustomizationPanel( + modifier = Modifier + .width(320.dp) + .fillMaxHeight() + .clip(RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)) + .background(JewelTheme.globalColors.borders.normal), + settings = settings, + capabilities = SvgCustomizationCapabilities(), + onClose = {}, + onSettingsChange = { settings = it }, + ) +} diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt index ac8aa9bff..2fa1dcd91 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt @@ -4,11 +4,9 @@ import io.github.composegears.valkyrie.settings.InMemorySettings import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.bootstrap.data.BootstrapRepository import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.SvgSizeCustomizer import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.toDisplayName import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.toStandardIconConfig @@ -49,8 +47,5 @@ class BootstrapUseCase( return FontByteArray(repository.loadFontBytes()) } - override suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { - val rawSvg = repository.downloadSvg(icon.name) - return SvgSizeCustomizer.applySettings(rawSvg, settings) - } + override suspend fun loadSvg(icon: StandardIcon): String = repository.downloadSvg(icon.name) } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt index 51464e50a..8b1813a3d 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt @@ -4,11 +4,9 @@ import io.github.composegears.valkyrie.settings.InMemorySettings import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.boxicons.data.BoxIconsRepository import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.SvgSizeCustomizer import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.toDisplayName import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.toStandardIconConfig @@ -52,10 +50,7 @@ class BoxIconsUseCase( return FontByteArray(repository.loadFontBytes()) } - override suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { - val rawSvg = repository.downloadSvg(icon.name) - return SvgSizeCustomizer.applySettings(rawSvg, settings) - } + override suspend fun loadSvg(icon: StandardIcon): String = repository.downloadSvg(icon.name) } private enum class BoxIconsStyle(val prefix: String, val value: IconStyle) { diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt index f8427c7b9..62c76ac81 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt @@ -16,6 +16,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.util.filterByCategory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -102,6 +103,7 @@ class StandardIconViewModel( style = selectedStyle, searchQuery = "", ), + customizationCapabilities = provider.customizationCapabilities, settings = SizeSettings(size = provider.persistentSize), selectedStyle = selectedStyle, ) @@ -145,7 +147,8 @@ class StandardIconViewModel( val currentState = stateRecord.value.safeAs() ?: return@launch runCatching { - val svgContent = provider.downloadSvg(icon, currentState.settings) + val latestSettings = stateRecord.value.safeAs()?.settings ?: currentState.settings + val svgContent = provider.downloadSvg(icon, latestSettings) _events.send( StandardIconEvent.IconDownloaded( @@ -264,6 +267,7 @@ sealed interface StandardState { data class Success( val config: StandardIconConfig, val gridItems: List = emptyList(), + val customizationCapabilities: SvgCustomizationCapabilities = SvgCustomizationCapabilities(), val selectedCategory: InferredCategory = InferredCategory.All, val selectedStyle: IconStyle? = null, val searchQuery: String = "", diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt index e0f9ac45a..e2700434c 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt @@ -46,8 +46,8 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.CategoryHea import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.IconCard import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.IconGrid import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.IconLoadingPlaceholder -import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.IconSizeCustomization import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.SidePanel +import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.SvgCustomizationPanel import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.InferredCategory @@ -175,7 +175,6 @@ private fun IconsContent( ) { val scope = rememberCoroutineScope() - var selectedIcon by rememberMutableState { null } var isSidePanelOpen by rememberMutableState { false } val lazyGridState = rememberLazyGridState() val shimmer = rememberShimmer() @@ -260,11 +259,8 @@ private fun IconsContent( iconContent = { icon -> IconCard( name = icon.displayName, - selected = icon == selectedIcon, - onClick = { - selectedIcon = icon - onSelectIcon(icon) - }, + selected = false, + onClick = { onSelectIcon(icon) }, iconContent = { FontIcon( modifier = Modifier.size(iconSizeDp), @@ -284,11 +280,11 @@ private fun IconsContent( isOpen = isSidePanelOpen, onClose = { isSidePanelOpen = false }, content = { - IconSizeCustomization( + SvgCustomizationPanel( settings = state.settings, + capabilities = state.customizationCapabilities, onSettingsChange = onSettingsChange, onClose = { isSidePanelOpen = false }, - sizeLabel = stringResource("web.import.font.customize.size"), ) }, ) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt index 521f701bf..0d99d6ce7 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt @@ -6,17 +6,28 @@ import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities interface StandardIconProvider { val providerName: String val stateKey: String val fontAlias: String val persistentSize: Int + val customizationCapabilities: SvgCustomizationCapabilities + get() = SvgCustomizationCapabilities() fun updatePersistentSize(value: Int) fun resolveFontWeight(style: IconStyle?): FontWeight = FontWeight.W400 suspend fun loadConfig(): StandardIconConfig suspend fun loadFontBytes(style: IconStyle? = null): FontByteArray - suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String + suspend fun loadSvg(icon: StandardIcon): String + + fun applySettings(svgContent: String, settings: SizeSettings): String { + return SvgSizeCustomizer.applySettings(svgContent, settings) + } + + suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { + return applySettings(loadSvg(icon), settings) + } } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt index 4214016df..fda8e9e30 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt @@ -2,6 +2,7 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.doma import io.github.composegears.valkyrie.sdk.utils.svg.SvgDomModifier import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import org.w3c.dom.Element /** * Utility for applying size settings to SVG content. @@ -11,11 +12,90 @@ object SvgSizeCustomizer { private const val ATTR_WIDTH = "width" private const val ATTR_HEIGHT = "height" + private const val ATTR_FILL = "fill" + private const val ATTR_STROKE = "stroke" + private const val ATTR_COLOR = "color" + private const val ATTR_TRANSFORM = "transform" + private const val ATTR_VIEW_BOX = "viewBox" + private const val CURRENT_COLOR = "currentColor" fun applySettings(svgContent: String, settings: SizeSettings): String { return SvgDomModifier.modify(svgContent) { svgElement -> svgElement.setAttribute(ATTR_WIDTH, settings.size.toString()) svgElement.setAttribute(ATTR_HEIGHT, settings.size.toString()) + + settings.color?.let { color -> + svgElement.setAttribute(ATTR_COLOR, color) + if (!svgElement.hasAttribute(ATTR_FILL)) { + svgElement.setAttribute(ATTR_FILL, color) + } + SvgDomModifier.updateAttributeConditionally(svgElement, ATTR_FILL, CURRENT_COLOR, color) + SvgDomModifier.updateAttributeConditionally(svgElement, ATTR_STROKE, CURRENT_COLOR, color) + } + + buildTransform(svgElement, settings)?.let { transform -> + svgElement.setAttribute(ATTR_TRANSFORM, transform) + } + } + } + + private fun buildTransform( + svgElement: Element, + settings: SizeSettings, + ): String? { + if ( + settings.rotation == SizeSettings.DEFAULT_ROTATION && + !settings.flipHorizontally && + !settings.flipVertically + ) { + return null + } + + val bounds = parseBounds(svgElement) + val transforms = buildList { + if (settings.rotation != SizeSettings.DEFAULT_ROTATION) { + add("rotate(${settings.rotation} ${bounds.centerX} ${bounds.centerY})") + } + if (settings.flipHorizontally) { + add("translate(${bounds.flipX} 0) scale(-1 1)") + } + if (settings.flipVertically) { + add("translate(0 ${bounds.flipY}) scale(1 -1)") + } } + + return transforms.joinToString(" ") } + + private fun parseBounds(svgElement: Element): SvgBounds { + val viewBox = svgElement.getAttribute(ATTR_VIEW_BOX) + .split(',', ' ') + .filter { it.isNotBlank() } + .mapNotNull { it.toDoubleOrNull() } + + if (viewBox.size == 4) { + return SvgBounds( + centerX = (viewBox[0] + viewBox[2] / 2).toString(), + centerY = (viewBox[1] + viewBox[3] / 2).toString(), + flipX = (viewBox[0] * 2 + viewBox[2]).toString(), + flipY = (viewBox[1] * 2 + viewBox[3]).toString(), + ) + } + + val width = svgElement.getAttribute(ATTR_WIDTH).toDoubleOrNull() ?: SizeSettings.DEFAULT_SIZE.toDouble() + val height = svgElement.getAttribute(ATTR_HEIGHT).toDoubleOrNull() ?: SizeSettings.DEFAULT_SIZE.toDouble() + return SvgBounds( + centerX = (width / 2).toString(), + centerY = (height / 2).toString(), + flipX = width.toString(), + flipY = height.toString(), + ) + } + + private data class SvgBounds( + val centerX: String, + val centerY: String, + val flipX: String, + val flipY: String, + ) } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt index d3b33822a..e91db5255 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt @@ -3,16 +3,23 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.mode import io.github.composegears.valkyrie.ui.screen.webimport.common.model.IconSettings /** - * Size settings for standard icon providers. - * Allows customization of icon dimensions (width/height). + * Shared SVG customization settings for standard icon providers. + * + * The current UI only exposes size, but the shared model also carries + * transformer-backed color and transform options used by the standard web import flow. */ data class SizeSettings( val size: Int = DEFAULT_SIZE, + val color: String? = null, + val rotation: Int = DEFAULT_ROTATION, + val flipHorizontally: Boolean = false, + val flipVertically: Boolean = false, ) : IconSettings { companion object { const val DEFAULT_SIZE = 24 const val MIN_SIZE = 16 const val MAX_SIZE = 48 + const val DEFAULT_ROTATION = 0 } init { @@ -20,5 +27,10 @@ data class SizeSettings( } override val isModified: Boolean - get() = size != DEFAULT_SIZE + get() = + size != DEFAULT_SIZE || + color != null || + rotation != DEFAULT_ROTATION || + flipHorizontally || + flipVertically } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgCustomizationCapabilities.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgCustomizationCapabilities.kt new file mode 100644 index 000000000..612f64ff9 --- /dev/null +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgCustomizationCapabilities.kt @@ -0,0 +1,7 @@ +package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model + +data class SvgCustomizationCapabilities( + val supportsColor: Boolean = true, + val supportsRotation: Boolean = true, + val supportsFlip: Boolean = true, +) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt index c1c4b7177..850a66b36 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt @@ -3,11 +3,9 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.lucide.doma import io.github.composegears.valkyrie.settings.InMemorySettings import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.SvgSizeCustomizer import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.toDisplayName import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.toStandardIconConfig @@ -51,8 +49,5 @@ class LucideUseCase( return FontByteArray(repository.loadFontBytes()) } - override suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { - val rawSvg = repository.downloadSvg(icon.name) - return SvgSizeCustomizer.applySettings(rawSvg, settings) - } + override suspend fun loadSvg(icon: StandardIcon): String = repository.downloadSvg(icon.name) } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt index 4faaa7a52..0e65ba2a9 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt @@ -3,12 +3,10 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.remix.domai import io.github.composegears.valkyrie.settings.InMemorySettings import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.SvgSizeCustomizer import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.toDisplayName import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.InferredCategory -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.toStandardIconConfig @@ -60,8 +58,5 @@ class RemixUseCase( return FontByteArray(repository.loadFontBytes()) } - override suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { - val rawSvg = repository.downloadSvg(icon.name) - return SvgSizeCustomizer.applySettings(rawSvg, settings) - } + override suspend fun loadSvg(icon: StandardIcon): String = repository.downloadSvg(icon.name) } diff --git a/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties b/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties index c6d8cf6b5..d052f0623 100644 --- a/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties +++ b/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties @@ -132,7 +132,21 @@ web.import.placeholder.empty=No icons found web.import.search.placeholder=Search icons web.import.font.customize.header=Customize web.import.font.customize.reset=Reset +web.import.font.customize.preview=Preview +web.import.font.customize.preview.empty=Select an icon to preview and import it +web.import.font.customize.preview.loading=Loading preview... +web.import.font.customize.color=Color +web.import.font.customize.color.placeholder=#4F8FBA +web.import.font.customize.color.custom=Custom color +web.import.font.customize.color.original=Original web.import.font.customize.size=Size +web.import.font.customize.rotate=Rotate +web.import.font.customize.flip=Flip +web.import.font.customize.flip.horizontal=Horizontal +web.import.font.customize.flip.vertical=Vertical +web.import.font.customize.none=None +web.import.font.customize.degrees={0}\u00B0 +web.import.font.customize.import=Import selected icon web.import.font.customize.px.suffix={0}px web.import.font.customize.material.fill=Fill web.import.font.customize.material.weight=Weight: From 849e83aba92b5b03022b87b714396a78f0fec271 Mon Sep 17 00:00:00 2001 From: t-regbs Date: Thu, 19 Mar 2026 22:38:34 +0000 Subject: [PATCH 2/4] feat: add white color option from presetColors list --- .../screen/webimport/common/ui/SvgCustomizationPanel.kt | 1 + .../webimport/standard/common/StandardImportScreenUI.kt | 8 ++++++-- .../standard/fontawesome/domain/FontAwesomeUseCase.kt | 7 ++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt index 1a2e6560c..94df5c03c 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt @@ -49,6 +49,7 @@ import org.jetbrains.jewel.ui.component.styling.LocalGroupHeaderStyle private val rotationOptions = listOf(0, 90, 180, 270) private val presetColors = listOf( + "#FFFFFF", "#F4CC3A", "#63D3AF", "#68A9E0", diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt index e2700434c..07d9d22f0 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt @@ -175,6 +175,7 @@ private fun IconsContent( ) { val scope = rememberCoroutineScope() + var selectedIcon by rememberMutableState { null } var isSidePanelOpen by rememberMutableState { false } val lazyGridState = rememberLazyGridState() val shimmer = rememberShimmer() @@ -259,8 +260,11 @@ private fun IconsContent( iconContent = { icon -> IconCard( name = icon.displayName, - selected = false, - onClick = { onSelectIcon(icon) }, + selected = icon == selectedIcon, + onClick = { + selectedIcon = icon + onSelectIcon(icon) + }, iconContent = { FontIcon( modifier = Modifier.size(iconSizeDp), diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt index 829f39766..1646d3e3f 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt @@ -4,11 +4,9 @@ import androidx.compose.ui.text.font.FontWeight import io.github.composegears.valkyrie.settings.InMemorySettings import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.SvgSizeCustomizer import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.toDisplayName import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.toStandardIconConfig @@ -66,10 +64,9 @@ class FontAwesomeUseCase( return FontByteArray(repository.loadFontBytes(styleId = styleId)) } - override suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { + override suspend fun loadSvg(icon: StandardIcon): String { val styleId = icon.style?.id ?: SOLID_STYLE_ID - val rawSvg = repository.downloadSvg(iconName = icon.name, styleId = styleId) - return SvgSizeCustomizer.applySettings(rawSvg, settings) + return repository.downloadSvg(iconName = icon.name, styleId = styleId) } private fun FontAwesomeIconMetadata.toExportName(style: IconStyle, stylesCount: Int): String { From 726dffb47990c4390c7d6ebd2d1debfe780563b3 Mon Sep 17 00:00:00 2001 From: t-regbs Date: Thu, 26 Mar 2026 20:37:07 +0000 Subject: [PATCH 3/4] refactor: add recently used colours and update in-memory settings to persist custom colors and recent colors --- .../valkyrie/service/PersistentSettings.kt | 12 +- .../valkyrie/settings/InMemorySettings.kt | 23 ++- .../webimport/common/model/IconSettings.kt | 2 +- .../common/ui/IconSizeCustomization.kt | 14 +- .../common/ui/SvgCustomizationPanel.kt | 179 ++++++++++++------ .../bootstrap/domain/BootstrapUseCase.kt | 11 ++ .../boxicons/domain/BoxIconsUseCase.kt | 11 ++ .../standard/common/StandardIconViewModel.kt | 56 +++++- .../standard/common/StandardImportScreenUI.kt | 18 +- .../common/domain/StandardIconProvider.kt | 15 +- ...zeCustomizer.kt => SvgImportCustomizer.kt} | 26 +-- .../{SizeSettings.kt => SvgImportSettings.kt} | 2 +- .../fontawesome/domain/FontAwesomeUseCase.kt | 11 ++ .../standard/lucide/domain/LucideUseCase.kt | 11 ++ .../standard/remix/domain/RemixUseCase.kt | 11 ++ .../common/domain/SvgImportCustomizerTest.kt | 100 ++++++++++ 16 files changed, 407 insertions(+), 95 deletions(-) rename tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/{SvgSizeCustomizer.kt => SvgImportCustomizer.kt} (78%) rename tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/{SizeSettings.kt => SvgImportSettings.kt} (97%) create mode 100644 tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizerTest.kt diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/service/PersistentSettings.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/service/PersistentSettings.kt index e50a46895..9b6c98d7a 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/service/PersistentSettings.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/service/PersistentSettings.kt @@ -14,7 +14,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_GRADE import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_OPTICAL_SIZE import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_WEIGHT -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings.Companion.DEFAULT_SIZE +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings.Companion.DEFAULT_SIZE @State(name = "Valkyrie.Settings", storages = [Storage("valkyrie_settings.xml")]) class PersistentSettings : SimplePersistentStateComponent(ValkyrieState()) { @@ -64,18 +64,28 @@ class PersistentSettings : SimplePersistentStateComponent(Valkyri // Lucide var lucideSize: Int by property(DEFAULT_SIZE) + var lucideLastCustomColor: String? by string() + var lucideRecentColors: String? by string() // Bootstrap var bootstrapSize: Int by property(DEFAULT_SIZE) + var bootstrapLastCustomColor: String? by string() + var bootstrapRecentColors: String? by string() // Remix var remixSize: Int by property(DEFAULT_SIZE) + var remixLastCustomColor: String? by string() + var remixRecentColors: String? by string() // BoxIcons var boxiconsSize: Int by property(DEFAULT_SIZE) + var boxiconsLastCustomColor: String? by string() + var boxiconsRecentColors: String? by string() // Font Awesome var fontAwesomeSize: Int by property(DEFAULT_SIZE) + var fontAwesomeLastCustomColor: String? by string() + var fontAwesomeRecentColors: String? by string() } companion object { diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt index e50b9298e..94d173f39 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt @@ -13,7 +13,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_GRADE import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_OPTICAL_SIZE import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_WEIGHT -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings.Companion.DEFAULT_SIZE +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings.Companion.DEFAULT_SIZE import java.util.Collections.emptyList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -66,10 +66,20 @@ class InMemorySettings(project: Project) { materialFontGrade = DEFAULT_GRADE materialFontOpticalSize = DEFAULT_OPTICAL_SIZE lucideSize = DEFAULT_SIZE + lucideLastCustomColor = "" + lucideRecentColors = "" bootstrapSize = DEFAULT_SIZE + bootstrapLastCustomColor = "" + bootstrapRecentColors = "" remixSize = DEFAULT_SIZE + remixLastCustomColor = "" + remixRecentColors = "" boxiconsSize = DEFAULT_SIZE + boxiconsLastCustomColor = "" + boxiconsRecentColors = "" fontAwesomeSize = DEFAULT_SIZE + fontAwesomeLastCustomColor = "" + fontAwesomeRecentColors = "" } fun updateUIState(uiState: SavedState) { @@ -136,3 +146,14 @@ fun PersistentSettings.ValkyrieState.updateNestedPack(packs: List) { fun PersistentSettings.ValkyrieState.updateOutputFormat(format: OutputFormat) { outputFormat = format.key } + +fun String?.toStringList(): List { + return orEmpty() + .split(",") + .map(String::trim) + .filter(String::isNotEmpty) +} + +fun List.toPersistedString(): String { + return joinToString(separator = ",") +} diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/model/IconSettings.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/model/IconSettings.kt index 96475d553..777e618fe 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/model/IconSettings.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/model/IconSettings.kt @@ -2,7 +2,7 @@ package io.github.composegears.valkyrie.ui.screen.webimport.common.model /** * Common interface for icon settings that can be applied during web import. - * Implemented by both standard providers (SizeSettings) and Material (FontSettings). + * Implemented by both standard providers (SvgImportSettings) and Material (FontSettings). */ interface IconSettings { val isModified: Boolean diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/IconSizeCustomization.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/IconSizeCustomization.kt index 476fc50f9..642499be9 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/IconSizeCustomization.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/IconSizeCustomization.kt @@ -21,7 +21,7 @@ import io.github.composegears.valkyrie.jewel.tooling.PreviewTheme import io.github.composegears.valkyrie.sdk.compose.foundation.layout.CenterVerticalRow import io.github.composegears.valkyrie.sdk.compose.foundation.layout.WeightSpacer import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.util.stringResource import kotlin.math.roundToInt import org.jetbrains.compose.ui.tooling.preview.Preview @@ -43,8 +43,8 @@ import org.jetbrains.jewel.ui.component.styling.LocalGroupHeaderStyle */ @Composable fun IconSizeCustomization( - settings: SizeSettings, - onSettingsChange: (SizeSettings) -> Unit, + settings: SvgImportSettings, + onSettingsChange: (SvgImportSettings) -> Unit, onClose: () -> Unit, sizeLabel: String, modifier: Modifier = Modifier, @@ -55,8 +55,8 @@ fun IconSizeCustomization( CustomizationToolbar( onClose = onClose, onReset = { - size = SizeSettings.DEFAULT_SIZE.toFloat() - onSettingsChange(SizeSettings()) + size = SvgImportSettings.DEFAULT_SIZE.toFloat() + onSettingsChange(SvgImportSettings()) }, isModified = settings.isModified, ) @@ -77,7 +77,7 @@ fun IconSizeCustomization( size = it onSettingsChange(settings.copy(size = size.roundToInt())) }, - valueRange = SizeSettings.MIN_SIZE.toFloat()..SizeSettings.MAX_SIZE.toFloat(), + valueRange = SvgImportSettings.MIN_SIZE.toFloat()..SvgImportSettings.MAX_SIZE.toFloat(), ) } } @@ -87,7 +87,7 @@ fun IconSizeCustomization( @Preview @Composable private fun IconSizeCustomizationPreview() = PreviewTheme(alignment = Alignment.TopEnd) { - var settings by rememberMutableState { SizeSettings() } + var settings by rememberMutableState { SvgImportSettings() } IconSizeCustomization( modifier = Modifier diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt index 94df5c03c..ac10c2702 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt @@ -1,76 +1,88 @@ -@file:Suppress("DEPRECATION") - package io.github.composegears.valkyrie.ui.screen.webimport.common.ui import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.dp -import com.intellij.ui.ColorChooser +import com.intellij.ui.ColorChooserService import io.github.composegears.valkyrie.jewel.HorizontalDivider import io.github.composegears.valkyrie.jewel.settings.DropdownSettingsRow import io.github.composegears.valkyrie.jewel.tooling.PreviewTheme import io.github.composegears.valkyrie.sdk.compose.foundation.layout.CenterVerticalRow -import io.github.composegears.valkyrie.sdk.compose.foundation.layout.Spacer import io.github.composegears.valkyrie.sdk.compose.foundation.layout.WeightSpacer import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings.Companion.MAX_SIZE +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings.Companion.MIN_SIZE import io.github.composegears.valkyrie.util.stringResource import java.awt.Color as AwtColor import kotlin.math.roundToInt import org.jetbrains.compose.ui.tooling.preview.Preview +import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.foundation.LocalComponent import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.CheckboxRow +import org.jetbrains.jewel.ui.component.HorizontallyScrollableContainer import org.jetbrains.jewel.ui.component.InfoText import org.jetbrains.jewel.ui.component.OutlinedButton import org.jetbrains.jewel.ui.component.Slider import org.jetbrains.jewel.ui.component.Text import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer import org.jetbrains.jewel.ui.component.styling.LocalGroupHeaderStyle +import org.jetbrains.jewel.ui.util.fromArgbHexStringOrNull +import org.jetbrains.jewel.ui.util.toArgbHexString private val rotationOptions = listOf(0, 90, 180, 270) -private val presetColors = listOf( - "#FFFFFF", - "#F4CC3A", - "#63D3AF", - "#68A9E0", - "#9D86E3", - "#FF9A62", -) +private const val RECENT_COLOR_SLOTS = 4 +private val presetColors = listOf("#FFFFFF") -private fun String.toComposeColor(): Color { - val awtColor = AwtColor.decode(this) - return Color(awtColor.red, awtColor.green, awtColor.blue) -} +private val defaultPickerColor = Color(0xFF68A9E0) + +private fun Color.toAwtColor(): AwtColor = AwtColor( + (red * 255).roundToInt(), + (green * 255).roundToInt(), + (blue * 255).roundToInt(), +) -private fun String.toAwtColorOrNull(): AwtColor? = runCatching { AwtColor.decode(this) }.getOrNull() +private fun AwtColor.toRgbHexString(): String = toArgbHexString() + .removePrefix("#") + .takeLast(6) + .uppercase() + .let { "#$it" } -private fun AwtColor.toHexColor(): String = String.format("#%02X%02X%02X", red, green, blue) +private fun String.toComposeColorOrNull(): Color? = Color.fromArgbHexStringOrNull(this) +@OptIn(ExperimentalJewelApi::class) @Composable fun SvgCustomizationPanel( - settings: SizeSettings, + settings: SvgImportSettings, + recentColors: List, + lastCustomColor: String?, capabilities: SvgCustomizationCapabilities, - onSettingsChange: (SizeSettings) -> Unit, + onSettingsChange: (SvgImportSettings) -> Unit, + onCustomColorPicked: (String) -> Unit, + onResetCustomization: () -> Unit, onClose: () -> Unit, modifier: Modifier = Modifier, ) { @@ -78,11 +90,12 @@ fun SvgCustomizationPanel( val customLabel = stringResource("web.import.font.customize.color.custom") val originalLabel = stringResource("web.import.font.customize.color.original") val component = LocalComponent.current + val customColor = lastCustomColor?.toComposeColorOrNull() Column(modifier = modifier) { CustomizationToolbar( onClose = onClose, - onReset = { onSettingsChange(SizeSettings()) }, + onReset = onResetCustomization, isModified = settings.isModified, ) HorizontalDivider(color = LocalGroupHeaderStyle.current.colors.divider) @@ -97,35 +110,52 @@ fun SvgCustomizationPanel( WeightSpacer() InfoText(text = settings.color ?: originalLabel) } - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - ColorSwatch( - label = noneLabel, - color = null, - selected = settings.color == null, - onClick = { onSettingsChange(settings.copy(color = null)) }, - ) - presetColors.forEach { hex -> + HorizontallyScrollableContainer { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { ColorSwatch( - label = hex, - color = hex.toComposeColor(), - selected = settings.color == hex, - onClick = { onSettingsChange(settings.copy(color = hex)) }, + label = noneLabel, + color = null, + selected = settings.color == null, + isNoneSwatch = true, + onClick = { onSettingsChange(settings.copy(color = null)) }, ) + presetColors.forEach { hex -> + ColorSwatch( + label = hex, + color = Color.fromArgbHexStringOrNull(hex) ?: defaultPickerColor, + selected = settings.color == hex, + onClick = { onSettingsChange(settings.copy(color = hex)) }, + ) + } + repeat(RECENT_COLOR_SLOTS) { index -> + val recentColorHex = recentColors.getOrNull(index) + ColorSwatch( + label = "", + color = recentColorHex?.toComposeColorOrNull(), + selected = recentColorHex != null && settings.color == recentColorHex, + isPlaceholder = recentColorHex == null, + onClick = { + recentColorHex?.let { onSettingsChange(settings.copy(color = it)) } + }, + ) + } } } OutlinedButton( onClick = { - val chosenColor = ColorChooser.chooseColor( - component, - customLabel, - settings.color?.toAwtColorOrNull() ?: AwtColor.decode("#68A9E0"), - false, + val chosenColor = ColorChooserService.getInstance().showDialog( + parent = component, + caption = customLabel, + preselectedColor = (settings.color?.let(Color::fromArgbHexStringOrNull) ?: customColor + ?: defaultPickerColor) + .toAwtColor(), + enableOpacity = false, ) - if (chosenColor != null) { - onSettingsChange(settings.copy(color = chosenColor.toHexColor())) + chosenColor?.let { + onCustomColorPicked(it.toRgbHexString()) } }, ) { @@ -133,10 +163,7 @@ fun SvgCustomizationPanel( } } - var size by rememberMutableState { settings.size.toFloat() } - LaunchedEffect(settings.size) { - size = settings.size.toFloat() - } + var size by rememberMutableState(settings.size) { settings.size.toFloat() } CenterVerticalRow(modifier = Modifier.padding(horizontal = 4.dp)) { Text(text = stringResource("web.import.font.customize.size")) WeightSpacer() @@ -148,7 +175,7 @@ fun SvgCustomizationPanel( size = it onSettingsChange(settings.copy(size = size.roundToInt())) }, - valueRange = SizeSettings.MIN_SIZE.toFloat()..SizeSettings.MAX_SIZE.toFloat(), + valueRange = MIN_SIZE.toFloat()..MAX_SIZE.toFloat(), ) if (capabilities.supportsRotation) { @@ -196,32 +223,64 @@ private fun ColorSwatch( label: String, color: Color?, selected: Boolean, + isPlaceholder: Boolean = false, + isNoneSwatch: Boolean = false, onClick: () -> Unit, ) { - val borderColor = when { - selected -> JewelTheme.globalColors.text.normal - else -> JewelTheme.globalColors.borders.normal + val colors = JewelTheme.globalColors + val borderColor = with(colors) { + when { + selected -> text.normal + isPlaceholder -> borders.normal.copy(alpha = 0.12f) + else -> borders.normal + } + } + val backgroundColor = when { + color != null -> color + isPlaceholder -> colors.panelBackground.copy(alpha = 0.38f) + else -> colors.panelBackground } Box( modifier = Modifier .size(32.dp) .clip(RoundedCornerShape(10.dp)) - .background(color ?: JewelTheme.globalColors.panelBackground) + .background(backgroundColor) .border(width = 2.dp, color = borderColor, shape = RoundedCornerShape(10.dp)) .clickable(onClick = onClick), contentAlignment = Alignment.Center, ) { - if (color == null) { + if (isNoneSwatch) { + val glyphColor = if (selected) colors.text.normal else colors.text.normal.copy(alpha = 0.8f) + NoneGlyph(color = glyphColor) + } else if (color == null && !isPlaceholder) { Text(text = label.take(1)) } } } +@Composable +private fun NoneGlyph(color: Color) { + Canvas(modifier = Modifier.size(15.dp)) { + val strokeWidth = 1.6.dp.toPx() + drawCircle( + color = color, + style = Stroke(width = strokeWidth), + ) + drawLine( + color = color, + start = Offset(x = size.width * 0.18f, y = size.height * 0.18f), + end = Offset(x = size.width * 0.82f, y = size.height * 0.82f), + strokeWidth = strokeWidth, + cap = StrokeCap.Round, + ) + } +} + @Preview @Composable private fun SvgCustomizationPanelPreview() = PreviewTheme(alignment = Alignment.TopEnd) { - var settings by rememberMutableState { SizeSettings(color = "#4F8FBA", rotation = 90) } + var settings by rememberMutableState { SvgImportSettings(color = "#4F8FBA", rotation = 90) } SvgCustomizationPanel( modifier = Modifier @@ -230,8 +289,12 @@ private fun SvgCustomizationPanelPreview() = PreviewTheme(alignment = Alignment. .clip(RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)) .background(JewelTheme.globalColors.borders.normal), settings = settings, + recentColors = listOf("#4F8FBA", "#1F8A70"), + lastCustomColor = "#4F8FBA", capabilities = SvgCustomizationCapabilities(), onClose = {}, onSettingsChange = { settings = it }, + onCustomColorPicked = { settings = settings.copy(color = it) }, + onResetCustomization = { settings = SvgImportSettings() }, ) } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt index 2fa1dcd91..3300bad39 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/bootstrap/domain/BootstrapUseCase.kt @@ -1,6 +1,8 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.bootstrap.domain import io.github.composegears.valkyrie.settings.InMemorySettings +import io.github.composegears.valkyrie.settings.toPersistedString +import io.github.composegears.valkyrie.settings.toStringList import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.bootstrap.data.BootstrapRepository import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider @@ -20,6 +22,8 @@ class BootstrapUseCase( override val stateKey: String = "bootstrap" override val fontAlias: String = "bootstrap-icons" override val persistentSize: Int = inMemorySettings.readState { bootstrapSize } + override val persistentLastCustomColor: String? = inMemorySettings.readState { bootstrapLastCustomColor }?.ifBlank { null } + override val persistentRecentColors: List = inMemorySettings.readState { bootstrapRecentColors }.toStringList() override fun updatePersistentSize(value: Int) { inMemorySettings.update { @@ -27,6 +31,13 @@ class BootstrapUseCase( } } + override fun updatePersistentCustomColors(lastCustomColor: String?, recentColors: List) { + inMemorySettings.update { + bootstrapLastCustomColor = lastCustomColor.orEmpty() + bootstrapRecentColors = recentColors.toPersistedString() + } + } + override suspend fun loadConfig(): StandardIconConfig { val codepoints = repository.loadCodepoints() diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt index 8b1813a3d..155bcbdb1 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/boxicons/domain/BoxIconsUseCase.kt @@ -1,6 +1,8 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.boxicons.domain import io.github.composegears.valkyrie.settings.InMemorySettings +import io.github.composegears.valkyrie.settings.toPersistedString +import io.github.composegears.valkyrie.settings.toStringList import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.boxicons.data.BoxIconsRepository import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider @@ -20,6 +22,8 @@ class BoxIconsUseCase( override val stateKey: String = "boxicons" override val fontAlias: String = "boxicons" override val persistentSize: Int = inMemorySettings.readState { boxiconsSize } + override val persistentLastCustomColor: String? = inMemorySettings.readState { boxiconsLastCustomColor }?.ifBlank { null } + override val persistentRecentColors: List = inMemorySettings.readState { boxiconsRecentColors }.toStringList() override fun updatePersistentSize(value: Int) { inMemorySettings.update { @@ -27,6 +31,13 @@ class BoxIconsUseCase( } } + override fun updatePersistentCustomColors(lastCustomColor: String?, recentColors: List) { + inMemorySettings.update { + boxiconsLastCustomColor = lastCustomColor.orEmpty() + boxiconsRecentColors = recentColors.toPersistedString() + } + } + override suspend fun loadConfig(): StandardIconConfig { val icons = repository.loadCodepoints().map { entry -> val style = entry.key.toStyle() diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt index 62c76ac81..302ed92e2 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt @@ -13,7 +13,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.model.GridItem import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.InferredCategory -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities @@ -104,7 +104,9 @@ class StandardIconViewModel( searchQuery = "", ), customizationCapabilities = provider.customizationCapabilities, - settings = SizeSettings(size = provider.persistentSize), + settings = SvgImportSettings(size = provider.persistentSize), + lastCustomColor = provider.persistentLastCustomColor, + recentColors = provider.persistentRecentColors, selectedStyle = selectedStyle, ) downloadFont(selectedStyle) @@ -213,7 +215,7 @@ class StandardIconViewModel( } } - fun updateSettings(settings: SizeSettings) { + fun updateSettings(settings: SvgImportSettings) { viewModelScope.launch(Dispatchers.Default) { provider.updatePersistentSize(settings.size) updateSuccess { state -> @@ -222,6 +224,50 @@ class StandardIconViewModel( } } + fun selectCustomColor(color: String) { + viewModelScope.launch(Dispatchers.Default) { + val currentState = stateRecord.value.safeAs() ?: return@launch + val updatedRecentColors = buildList { + add(color) + currentState.recentColors + .asSequence() + .filter { it != color } + .forEach(::add) + }.take(4) + + provider.updatePersistentCustomColors( + lastCustomColor = color, + recentColors = updatedRecentColors, + ) + + updateSuccess { state -> + state.copy( + settings = state.settings.copy(color = color), + lastCustomColor = color, + recentColors = updatedRecentColors, + ) + } + } + } + + fun resetCustomization() { + viewModelScope.launch(Dispatchers.Default) { + provider.updatePersistentSize(SvgImportSettings.DEFAULT_SIZE) + provider.updatePersistentCustomColors( + lastCustomColor = null, + recentColors = emptyList(), + ) + + updateSuccess { state -> + state.copy( + settings = SvgImportSettings(), + lastCustomColor = null, + recentColors = emptyList(), + ) + } + } + } + private fun prefetchStyleFonts(styles: List, selectedStyleId: String?) { prefetchJob?.cancel() prefetchJob = viewModelScope.launch { @@ -271,7 +317,9 @@ sealed interface StandardState { val selectedCategory: InferredCategory = InferredCategory.All, val selectedStyle: IconStyle? = null, val searchQuery: String = "", - val settings: SizeSettings = SizeSettings(), + val settings: SvgImportSettings = SvgImportSettings(), + val lastCustomColor: String? = null, + val recentColors: List = emptyList(), val fontByteArray: FontByteArray? = null, ) : StandardState diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt index 07d9d22f0..611a5ce9f 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt @@ -51,7 +51,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.SvgCustomiz import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.InferredCategory -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.ui.StandardTopActions import io.github.composegears.valkyrie.util.stringResource @@ -91,6 +91,8 @@ internal fun StandardImportScreen( onSelectStyle = viewModel::selectStyle, onSearchQueryChange = viewModel::updateSearchQuery, onSettingsChange = viewModel::updateSettings, + onCustomColorPicked = viewModel::selectCustomColor, + onResetCustomization = viewModel::resetCustomization, modifier = modifier, ) } @@ -106,7 +108,9 @@ private fun StandardImportScreenUI( onSelectCategory: (InferredCategory) -> Unit, onSelectStyle: (IconStyle) -> Unit, onSearchQueryChange: (String) -> Unit, - onSettingsChange: (SizeSettings) -> Unit, + onSettingsChange: (SvgImportSettings) -> Unit, + onCustomColorPicked: (String) -> Unit, + onResetCustomization: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -155,6 +159,8 @@ private fun StandardImportScreenUI( onSelectStyle = onSelectStyle, onSearchQueryChange = onSearchQueryChange, onSettingsChange = onSettingsChange, + onCustomColorPicked = onCustomColorPicked, + onResetCustomization = onResetCustomization, ) } } @@ -171,7 +177,9 @@ private fun IconsContent( onSelectCategory: (InferredCategory) -> Unit, onSelectStyle: (IconStyle) -> Unit, onSearchQueryChange: (String) -> Unit, - onSettingsChange: (SizeSettings) -> Unit, + onSettingsChange: (SvgImportSettings) -> Unit, + onCustomColorPicked: (String) -> Unit, + onResetCustomization: () -> Unit, ) { val scope = rememberCoroutineScope() @@ -286,8 +294,12 @@ private fun IconsContent( content = { SvgCustomizationPanel( settings = state.settings, + recentColors = state.recentColors, + lastCustomColor = state.lastCustomColor, capabilities = state.customizationCapabilities, onSettingsChange = onSettingsChange, + onCustomColorPicked = onCustomColorPicked, + onResetCustomization = onResetCustomization, onClose = { isSidePanelOpen = false }, ) }, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt index 0d99d6ce7..a70a4beae 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt @@ -3,7 +3,7 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.doma import androidx.compose.ui.text.font.FontWeight import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities @@ -13,21 +13,22 @@ interface StandardIconProvider { val stateKey: String val fontAlias: String val persistentSize: Int + val persistentLastCustomColor: String? + val persistentRecentColors: List val customizationCapabilities: SvgCustomizationCapabilities get() = SvgCustomizationCapabilities() fun updatePersistentSize(value: Int) + fun updatePersistentCustomColors(lastCustomColor: String?, recentColors: List) fun resolveFontWeight(style: IconStyle?): FontWeight = FontWeight.W400 suspend fun loadConfig(): StandardIconConfig suspend fun loadFontBytes(style: IconStyle? = null): FontByteArray suspend fun loadSvg(icon: StandardIcon): String - fun applySettings(svgContent: String, settings: SizeSettings): String { - return SvgSizeCustomizer.applySettings(svgContent, settings) - } + fun applySettings(svgContent: String, settings: SvgImportSettings): String = + SvgImportCustomizer.applySettings(svgContent, settings) - suspend fun downloadSvg(icon: StandardIcon, settings: SizeSettings): String { - return applySettings(loadSvg(icon), settings) - } + suspend fun downloadSvg(icon: StandardIcon, settings: SvgImportSettings): String = + applySettings(loadSvg(icon), settings) } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizer.kt similarity index 78% rename from tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt rename to tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizer.kt index fda8e9e30..22f1712fe 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgSizeCustomizer.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizer.kt @@ -1,14 +1,15 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain import io.github.composegears.valkyrie.sdk.utils.svg.SvgDomModifier -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SizeSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings.Companion.DEFAULT_SIZE import org.w3c.dom.Element /** - * Utility for applying size settings to SVG content. + * Utility for applying SVG import settings to SVG content. * Used by standard icon providers (Lucide, Bootstrap, etc.). */ -object SvgSizeCustomizer { +object SvgImportCustomizer { private const val ATTR_WIDTH = "width" private const val ATTR_HEIGHT = "height" @@ -19,7 +20,7 @@ object SvgSizeCustomizer { private const val ATTR_VIEW_BOX = "viewBox" private const val CURRENT_COLOR = "currentColor" - fun applySettings(svgContent: String, settings: SizeSettings): String { + fun applySettings(svgContent: String, settings: SvgImportSettings): String { return SvgDomModifier.modify(svgContent) { svgElement -> svgElement.setAttribute(ATTR_WIDTH, settings.size.toString()) svgElement.setAttribute(ATTR_HEIGHT, settings.size.toString()) @@ -29,8 +30,10 @@ object SvgSizeCustomizer { if (!svgElement.hasAttribute(ATTR_FILL)) { svgElement.setAttribute(ATTR_FILL, color) } - SvgDomModifier.updateAttributeConditionally(svgElement, ATTR_FILL, CURRENT_COLOR, color) - SvgDomModifier.updateAttributeConditionally(svgElement, ATTR_STROKE, CURRENT_COLOR, color) + SvgDomModifier.apply { + updateAttributeConditionally(svgElement, ATTR_FILL, CURRENT_COLOR, color) + updateAttributeConditionally(svgElement, ATTR_STROKE, CURRENT_COLOR, color) + } } buildTransform(svgElement, settings)?.let { transform -> @@ -41,10 +44,10 @@ object SvgSizeCustomizer { private fun buildTransform( svgElement: Element, - settings: SizeSettings, + settings: SvgImportSettings, ): String? { if ( - settings.rotation == SizeSettings.DEFAULT_ROTATION && + settings.rotation == SvgImportSettings.DEFAULT_ROTATION && !settings.flipHorizontally && !settings.flipVertically ) { @@ -53,7 +56,7 @@ object SvgSizeCustomizer { val bounds = parseBounds(svgElement) val transforms = buildList { - if (settings.rotation != SizeSettings.DEFAULT_ROTATION) { + if (settings.rotation != SvgImportSettings.DEFAULT_ROTATION) { add("rotate(${settings.rotation} ${bounds.centerX} ${bounds.centerY})") } if (settings.flipHorizontally) { @@ -63,7 +66,6 @@ object SvgSizeCustomizer { add("translate(0 ${bounds.flipY}) scale(1 -1)") } } - return transforms.joinToString(" ") } @@ -82,8 +84,8 @@ object SvgSizeCustomizer { ) } - val width = svgElement.getAttribute(ATTR_WIDTH).toDoubleOrNull() ?: SizeSettings.DEFAULT_SIZE.toDouble() - val height = svgElement.getAttribute(ATTR_HEIGHT).toDoubleOrNull() ?: SizeSettings.DEFAULT_SIZE.toDouble() + val width = svgElement.getAttribute(ATTR_WIDTH).toDoubleOrNull() ?: DEFAULT_SIZE.toDouble() + val height = svgElement.getAttribute(ATTR_HEIGHT).toDoubleOrNull() ?: DEFAULT_SIZE.toDouble() return SvgBounds( centerX = (width / 2).toString(), centerY = (height / 2).toString(), diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgImportSettings.kt similarity index 97% rename from tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt rename to tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgImportSettings.kt index e91db5255..11d1bed44 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SizeSettings.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/model/SvgImportSettings.kt @@ -8,7 +8,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.model.IconSett * The current UI only exposes size, but the shared model also carries * transformer-backed color and transform options used by the standard web import flow. */ -data class SizeSettings( +data class SvgImportSettings( val size: Int = DEFAULT_SIZE, val color: String? = null, val rotation: Int = DEFAULT_ROTATION, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt index 1646d3e3f..06e051fd9 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/fontawesome/domain/FontAwesomeUseCase.kt @@ -2,6 +2,8 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.fontawesome import androidx.compose.ui.text.font.FontWeight import io.github.composegears.valkyrie.settings.InMemorySettings +import io.github.composegears.valkyrie.settings.toPersistedString +import io.github.composegears.valkyrie.settings.toStringList import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags @@ -22,6 +24,8 @@ class FontAwesomeUseCase( override val stateKey: String = "fontawesome" override val fontAlias: String = "fontawesome" override val persistentSize: Int = inMemorySettings.readState { fontAwesomeSize } + override val persistentLastCustomColor: String? = inMemorySettings.readState { fontAwesomeLastCustomColor }?.ifBlank { null } + override val persistentRecentColors: List = inMemorySettings.readState { fontAwesomeRecentColors }.toStringList() override fun updatePersistentSize(value: Int) { inMemorySettings.update { @@ -29,6 +33,13 @@ class FontAwesomeUseCase( } } + override fun updatePersistentCustomColors(lastCustomColor: String?, recentColors: List) { + inMemorySettings.update { + fontAwesomeLastCustomColor = lastCustomColor.orEmpty() + fontAwesomeRecentColors = recentColors.toPersistedString() + } + } + override fun resolveFontWeight(style: IconStyle?): FontWeight { return if (style?.id == SOLID_STYLE_ID) FontWeight.W900 else FontWeight.W400 } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt index 850a66b36..eea74a9e0 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/lucide/domain/LucideUseCase.kt @@ -1,6 +1,8 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.lucide.domain import io.github.composegears.valkyrie.settings.InMemorySettings +import io.github.composegears.valkyrie.settings.toPersistedString +import io.github.composegears.valkyrie.settings.toStringList import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags @@ -20,6 +22,8 @@ class LucideUseCase( override val stateKey: String = "lucide" override val fontAlias: String = "lucide" override val persistentSize: Int = inMemorySettings.readState { lucideSize } + override val persistentLastCustomColor: String? = inMemorySettings.readState { lucideLastCustomColor }?.ifBlank { null } + override val persistentRecentColors: List = inMemorySettings.readState { lucideRecentColors }.toStringList() override fun updatePersistentSize(value: Int) { inMemorySettings.update { @@ -27,6 +31,13 @@ class LucideUseCase( } } + override fun updatePersistentCustomColors(lastCustomColor: String?, recentColors: List) { + inMemorySettings.update { + lucideLastCustomColor = lastCustomColor.orEmpty() + lucideRecentColors = recentColors.toPersistedString() + } + } + override suspend fun loadConfig(): StandardIconConfig { val iconMetadataList = repository.loadIconList() val codepoints = repository.loadCodepoints() diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt index 0e65ba2a9..2b09a79e8 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/remix/domain/RemixUseCase.kt @@ -1,6 +1,8 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.remix.domain import io.github.composegears.valkyrie.settings.InMemorySettings +import io.github.composegears.valkyrie.settings.toPersistedString +import io.github.composegears.valkyrie.settings.toStringList import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.inferCategoryFromTags @@ -21,6 +23,8 @@ class RemixUseCase( override val stateKey: String = "remix" override val fontAlias: String = "remixicon" override val persistentSize: Int = inMemorySettings.readState { remixSize } + override val persistentLastCustomColor: String? = inMemorySettings.readState { remixLastCustomColor }?.ifBlank { null } + override val persistentRecentColors: List = inMemorySettings.readState { remixRecentColors }.toStringList() override fun updatePersistentSize(value: Int) { inMemorySettings.update { @@ -28,6 +32,13 @@ class RemixUseCase( } } + override fun updatePersistentCustomColors(lastCustomColor: String?, recentColors: List) { + inMemorySettings.update { + remixLastCustomColor = lastCustomColor.orEmpty() + remixRecentColors = recentColors.toPersistedString() + } + } + override suspend fun loadConfig(): StandardIconConfig { val iconMetadata = repository.loadIconList() diff --git a/tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizerTest.kt b/tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizerTest.kt new file mode 100644 index 000000000..0229adc4b --- /dev/null +++ b/tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/SvgImportCustomizerTest.kt @@ -0,0 +1,100 @@ +package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain + +import assertk.assertThat +import assertk.assertions.contains +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings +import org.junit.jupiter.api.Test + +class SvgImportCustomizerTest { + + @Test + fun `applySettings uses viewBox center for rotation`() { + val svg = """ + + """.trimIndent() + + val result = SvgImportCustomizer.applySettings( + svgContent = svg, + settings = SvgImportSettings(rotation = 90), + ) + assertThat(result).contains("transform=\"rotate(90 22.0 32.0)\"") + } + + @Test + fun `applySettings uses viewBox bounds for horizontal flip`() { + val svg = """ + + """.trimIndent() + + val result = SvgImportCustomizer.applySettings( + svgContent = svg, + settings = SvgImportSettings(flipHorizontally = true), + ) + assertThat(result).contains("transform=\"translate(44.0 0) scale(-1 1)\"") + } + + @Test + fun `applySettings uses viewBox bounds for vertical flip`() { + val svg = """ + + """.trimIndent() + + val result = SvgImportCustomizer.applySettings( + svgContent = svg, + settings = SvgImportSettings(flipVertically = true), + ) + assertThat(result).contains("transform=\"translate(0 64.0) scale(1 -1)\"") + } + + @Test + fun `applySettings falls back to width and height when viewBox is missing`() { + val svg = """ + + """.trimIndent() + + val result = SvgImportCustomizer.applySettings( + svgContent = svg, + settings = SvgImportSettings( + rotation = 90, + flipHorizontally = true, + flipVertically = true, + ), + ) + + assertThat(result).apply { + contains("rotate(90 12.0 12.0)") + contains("translate(24.0 0) scale(-1 1)") + contains("translate(0 24.0) scale(1 -1)") + } + } + + @Test + fun `applySettings updates currentColor fill and stroke`() { + val svg = """ + + """.trimIndent() + + val result = SvgImportCustomizer.applySettings( + svgContent = svg, + settings = SvgImportSettings(color = "#6913E0"), + ) + assertThat(result).apply { + contains("color=\"#6913E0\"") + contains("fill=\"#6913E0\"") + contains("stroke=\"#6913E0\"") + } + } + + @Test + fun `applySettings sets fill when color is provided and fill is absent`() { + val svg = """ + + """.trimIndent() + + val result = SvgImportCustomizer.applySettings( + svgContent = svg, + settings = SvgImportSettings(color = "#FFFFFF"), + ) + assertThat(result).contains("fill=\"#FFFFFF\"") + } +} From e5afe409c62d66e61222181e210f961c3542267a Mon Sep 17 00:00:00 2001 From: t-regbs Date: Thu, 26 Mar 2026 21:26:35 +0000 Subject: [PATCH 4/4] chore: update changelog and fix linting --- tools/idea-plugin/CHANGELOG.md | 1 + .../webimport/common/ui/SvgCustomizationPanel.kt | 16 +++++++++------- .../standard/common/StandardIconViewModel.kt | 2 +- .../standard/common/StandardImportScreenUI.kt | 12 ++++++------ .../common/domain/StandardIconProvider.kt | 8 +++----- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tools/idea-plugin/CHANGELOG.md b/tools/idea-plugin/CHANGELOG.md index 2a46e190c..39bb24cfb 100644 --- a/tools/idea-plugin/CHANGELOG.md +++ b/tools/idea-plugin/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - [Web Import] Add `Font Awesome` icons provider +- [Web Import] Add SVG customization controls for standard icon providers, including size, rotation, flips, and persistent custom/recent colors ## 1.4.0 - 2026-03-15 diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt index ac10c2702..720b40a5f 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SvgCustomizationPanel.kt @@ -1,9 +1,9 @@ package io.github.composegears.valkyrie.ui.screen.webimport.common.ui +import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -18,8 +18,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke @@ -81,7 +81,7 @@ fun SvgCustomizationPanel( lastCustomColor: String?, capabilities: SvgCustomizationCapabilities, onSettingsChange: (SvgImportSettings) -> Unit, - onCustomColorPicked: (String) -> Unit, + onPickCustomColor: (String) -> Unit, onResetCustomization: () -> Unit, onClose: () -> Unit, modifier: Modifier = Modifier, @@ -149,13 +149,15 @@ fun SvgCustomizationPanel( val chosenColor = ColorChooserService.getInstance().showDialog( parent = component, caption = customLabel, - preselectedColor = (settings.color?.let(Color::fromArgbHexStringOrNull) ?: customColor - ?: defaultPickerColor) + preselectedColor = ( + settings.color?.let(Color::fromArgbHexStringOrNull) ?: customColor + ?: defaultPickerColor + ) .toAwtColor(), enableOpacity = false, ) chosenColor?.let { - onCustomColorPicked(it.toRgbHexString()) + onPickCustomColor(it.toRgbHexString()) } }, ) { @@ -294,7 +296,7 @@ private fun SvgCustomizationPanelPreview() = PreviewTheme(alignment = Alignment. capabilities = SvgCustomizationCapabilities(), onClose = {}, onSettingsChange = { settings = it }, - onCustomColorPicked = { settings = settings.copy(color = it) }, + onPickCustomColor = { settings = settings.copy(color = it) }, onResetCustomization = { settings = SvgImportSettings() }, ) } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt index 302ed92e2..300556467 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardIconViewModel.kt @@ -13,10 +13,10 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.model.GridItem import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.InferredCategory -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.util.filterByCategory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt index 611a5ce9f..a207d5bb3 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/StandardImportScreenUI.kt @@ -51,8 +51,8 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.SvgCustomiz import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.domain.StandardIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.InferredCategory -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.ui.StandardTopActions import io.github.composegears.valkyrie.util.stringResource import kotlinx.coroutines.launch @@ -91,7 +91,7 @@ internal fun StandardImportScreen( onSelectStyle = viewModel::selectStyle, onSearchQueryChange = viewModel::updateSearchQuery, onSettingsChange = viewModel::updateSettings, - onCustomColorPicked = viewModel::selectCustomColor, + onPickCustomColor = viewModel::selectCustomColor, onResetCustomization = viewModel::resetCustomization, modifier = modifier, ) @@ -109,7 +109,7 @@ private fun StandardImportScreenUI( onSelectStyle: (IconStyle) -> Unit, onSearchQueryChange: (String) -> Unit, onSettingsChange: (SvgImportSettings) -> Unit, - onCustomColorPicked: (String) -> Unit, + onPickCustomColor: (String) -> Unit, onResetCustomization: () -> Unit, modifier: Modifier = Modifier, ) { @@ -159,7 +159,7 @@ private fun StandardImportScreenUI( onSelectStyle = onSelectStyle, onSearchQueryChange = onSearchQueryChange, onSettingsChange = onSettingsChange, - onCustomColorPicked = onCustomColorPicked, + onPickCustomColor = onPickCustomColor, onResetCustomization = onResetCustomization, ) } @@ -178,7 +178,7 @@ private fun IconsContent( onSelectStyle: (IconStyle) -> Unit, onSearchQueryChange: (String) -> Unit, onSettingsChange: (SvgImportSettings) -> Unit, - onCustomColorPicked: (String) -> Unit, + onPickCustomColor: (String) -> Unit, onResetCustomization: () -> Unit, ) { val scope = rememberCoroutineScope() @@ -298,7 +298,7 @@ private fun IconsContent( lastCustomColor = state.lastCustomColor, capabilities = state.customizationCapabilities, onSettingsChange = onSettingsChange, - onCustomColorPicked = onCustomColorPicked, + onPickCustomColor = onPickCustomColor, onResetCustomization = onResetCustomization, onClose = { isSidePanelOpen = false }, ) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt index a70a4beae..fbf6aeb16 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/standard/common/domain/StandardIconProvider.kt @@ -3,10 +3,10 @@ package io.github.composegears.valkyrie.ui.screen.webimport.standard.common.doma import androidx.compose.ui.text.font.FontWeight import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.IconStyle -import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIcon import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.StandardIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgCustomizationCapabilities +import io.github.composegears.valkyrie.ui.screen.webimport.standard.common.model.SvgImportSettings interface StandardIconProvider { val providerName: String @@ -26,9 +26,7 @@ interface StandardIconProvider { suspend fun loadFontBytes(style: IconStyle? = null): FontByteArray suspend fun loadSvg(icon: StandardIcon): String - fun applySettings(svgContent: String, settings: SvgImportSettings): String = - SvgImportCustomizer.applySettings(svgContent, settings) + fun applySettings(svgContent: String, settings: SvgImportSettings): String = SvgImportCustomizer.applySettings(svgContent, settings) - suspend fun downloadSvg(icon: StandardIcon, settings: SvgImportSettings): String = - applySettings(loadSvg(icon), settings) + suspend fun downloadSvg(icon: StandardIcon, settings: SvgImportSettings): String = applySettings(loadSvg(icon), settings) }