From b1910de2e61f6b8b2492b9847ba7f35eec67317a Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 7 Feb 2025 11:24:30 +0100 Subject: [PATCH 01/50] Welcome Screen: Initial Frame --- .../processing/app/ui/Welcome.java | 0 app/src/processing/app/ui/Welcome.kt | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+) rename app/{src => ant}/processing/app/ui/Welcome.java (100%) create mode 100644 app/src/processing/app/ui/Welcome.kt diff --git a/app/src/processing/app/ui/Welcome.java b/app/ant/processing/app/ui/Welcome.java similarity index 100% rename from app/src/processing/app/ui/Welcome.java rename to app/ant/processing/app/ui/Welcome.java diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt new file mode 100644 index 0000000000..d7aa9a501b --- /dev/null +++ b/app/src/processing/app/ui/Welcome.kt @@ -0,0 +1,54 @@ +package processing.app.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.ComposePanel +import androidx.compose.ui.unit.dp +import com.formdev.flatlaf.util.SystemInfo +import processing.app.Base +import processing.app.contrib.ui.contributionsManager +import processing.app.ui.theme.Locale +import java.io.IOException +import javax.swing.JFrame +import javax.swing.SwingUtilities + +class Welcome @Throws(IOException::class) constructor(base: Base) { + init { + SwingUtilities.invokeLater { + + + JFrame(Locale()["menu.help.welcome"]).apply{ + val mac = SystemInfo.isMacFullWindowContentSupported + + rootPane.putClientProperty("apple.awt.transparentTitleBar", mac) + rootPane.putClientProperty("apple.awt.fullWindowContent", mac) + + defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE + add(ComposePanel().apply { + setContent { + Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { + welcome() + } + } + }) + + pack() + + setLocationRelativeTo(null) + + isVisible = true + + } + } + } + + @Composable + fun welcome() { + Box(modifier = Modifier.sizeIn(815.dp, 450.dp)){ + + } + } +} \ No newline at end of file From bd3a77ef17a456aa2ceb281cc2cf293f8be9d685 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 7 Feb 2025 12:09:58 +0100 Subject: [PATCH 02/50] Welcome Screen: Window Abstraction --- app/src/processing/app/ui/Welcome.kt | 47 ++++-------- app/src/processing/app/ui/WelcomeToBeta.kt | 79 +------------------- app/src/processing/app/ui/theme/Button.kt | 59 +++++++++++++++ app/src/processing/app/ui/theme/Window.kt | 85 ++++++++++++++++++++++ 4 files changed, 163 insertions(+), 107 deletions(-) create mode 100644 app/src/processing/app/ui/theme/Button.kt create mode 100644 app/src/processing/app/ui/theme/Window.kt diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index d7aa9a501b..8743282d62 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -1,54 +1,37 @@ package processing.app.ui import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposePanel import androidx.compose.ui.unit.dp -import com.formdev.flatlaf.util.SystemInfo import processing.app.Base -import processing.app.contrib.ui.contributionsManager -import processing.app.ui.theme.Locale +import processing.app.ui.theme.PDEWindow +import processing.app.ui.theme.pdeapplication import java.io.IOException -import javax.swing.JFrame import javax.swing.SwingUtilities class Welcome @Throws(IOException::class) constructor(base: Base) { init { SwingUtilities.invokeLater { - - - JFrame(Locale()["menu.help.welcome"]).apply{ - val mac = SystemInfo.isMacFullWindowContentSupported - - rootPane.putClientProperty("apple.awt.transparentTitleBar", mac) - rootPane.putClientProperty("apple.awt.fullWindowContent", mac) - - defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE - add(ComposePanel().apply { - setContent { - Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { - welcome() - } - } - }) - - pack() - - setLocationRelativeTo(null) - - isVisible = true - + PDEWindow("menu.help.welcome") { + welcome() } } } + companion object { + @Composable + fun welcome() { + Box(modifier = Modifier.sizeIn(815.dp, 450.dp)){ - @Composable - fun welcome() { - Box(modifier = Modifier.sizeIn(815.dp, 450.dp)){ + } + } + @JvmStatic + fun main(args: Array) { + pdeapplication("menu.help.welcome") { + welcome() + } } } } \ No newline at end of file diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index ddffd42d3e..ab0aaa9e17 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -29,10 +29,7 @@ import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import com.formdev.flatlaf.util.SystemInfo -import processing.app.ui.theme.LocalLocale -import processing.app.ui.theme.LocalTheme -import processing.app.ui.theme.Locale -import processing.app.ui.theme.ProcessingTheme +import processing.app.ui.theme.* import java.awt.Cursor import java.awt.Dimension import java.awt.event.KeyAdapter @@ -46,38 +43,12 @@ import javax.swing.SwingUtilities class WelcomeToBeta { companion object{ val windowSize = Dimension(400, 200) - val windowTitle = Locale()["beta.window.title"] @JvmStatic fun showWelcomeToBeta() { - val mac = SystemInfo.isMacFullWindowContentSupported SwingUtilities.invokeLater { - JFrame(windowTitle).apply { - val close = { dispose() } - rootPane.putClientProperty("apple.awt.transparentTitleBar", mac) - rootPane.putClientProperty("apple.awt.fullWindowContent", mac) - defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE - contentPane.add(ComposePanel().apply { - size = windowSize - setContent { - ProcessingTheme { - Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { - welcomeToBeta(close) - } - } - } - }) - pack() - background = java.awt.Color.white - setLocationRelativeTo(null) - addKeyListener(object : KeyAdapter() { - override fun keyPressed(e: KeyEvent) { - if (e.keyCode == KeyEvent.VK_ESCAPE) close() - } - }) - isResizable = false - isVisible = true - requestFocus() + PDEWindow("beta.window.title") { + welcomeToBeta() } } } @@ -129,48 +100,6 @@ class WelcomeToBeta { } } } - @OptIn(ExperimentalComposeUiApi::class) - @Composable - fun PDEButton(onClick: () -> Unit, content: @Composable BoxScope.() -> Unit) { - val theme = LocalTheme.current - - var hover by remember { mutableStateOf(false) } - var clicked by remember { mutableStateOf(false) } - val offset by animateFloatAsState(if (hover) -5f else 5f) - val color by animateColorAsState(if(clicked) colors.primaryVariant else colors.primary) - - Box(modifier = Modifier.padding(end = 5.dp, top = 5.dp)) { - Box( - modifier = Modifier - .offset((-offset).dp, (offset).dp) - .background(theme.getColor("toolbar.button.pressed.field")) - .matchParentSize() - ) - Box( - modifier = Modifier - .onPointerEvent(PointerEventType.Press) { - clicked = true - } - .onPointerEvent(PointerEventType.Release) { - clicked = false - onClick() - } - .onPointerEvent(PointerEventType.Enter) { - hover = true - } - .onPointerEvent(PointerEventType.Exit) { - hover = false - } - .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) - .background(color) - .padding(10.dp) - .sizeIn(minWidth = 100.dp), - contentAlignment = Alignment.Center, - content = content - ) - } - } - @JvmStatic fun main(args: Array) { @@ -180,7 +109,7 @@ class WelcomeToBeta { position = WindowPosition(Alignment.Center) ) - Window(onCloseRequest = ::exitApplication, state = windowState, title = windowTitle) { + Window(onCloseRequest = ::exitApplication, state = windowState, title = Locale()["beta.window.title"]) { ProcessingTheme { Surface(color = colors.background) { welcomeToBeta { diff --git a/app/src/processing/app/ui/theme/Button.kt b/app/src/processing/app/ui/theme/Button.kt new file mode 100644 index 0000000000..5d2923ab0f --- /dev/null +++ b/app/src/processing/app/ui/theme/Button.kt @@ -0,0 +1,59 @@ +package processing.app.ui.theme + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.unit.dp +import java.awt.Cursor + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun PDEButton(onClick: () -> Unit, content: @Composable BoxScope.() -> Unit) { + val theme = LocalTheme.current + + var hover by remember { mutableStateOf(false) } + var clicked by remember { mutableStateOf(false) } + val offset by animateFloatAsState(if (hover) -5f else 5f) + val color by animateColorAsState(if(clicked) colors.primaryVariant else colors.primary) + + Box(modifier = Modifier.padding(end = 5.dp, top = 5.dp)) { + Box( + modifier = Modifier + .offset((-offset).dp, (offset).dp) + .background(theme.getColor("toolbar.button.pressed.field")) + .matchParentSize() + ) + Box( + modifier = Modifier + .onPointerEvent(PointerEventType.Press) { + clicked = true + } + .onPointerEvent(PointerEventType.Release) { + clicked = false + onClick() + } + .onPointerEvent(PointerEventType.Enter) { + hover = true + } + .onPointerEvent(PointerEventType.Exit) { + hover = false + } + .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) + .background(color) + .padding(10.dp) + .sizeIn(minWidth = 100.dp), + contentAlignment = Alignment.Center, + content = content + ) + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt new file mode 100644 index 0000000000..eafa33a236 --- /dev/null +++ b/app/src/processing/app/ui/theme/Window.kt @@ -0,0 +1,85 @@ +package processing.app.ui.theme + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.ComposePanel +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import com.formdev.flatlaf.util.SystemInfo +import processing.app.ui.WelcomeToBeta.Companion.welcomeToBeta + +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent +import javax.swing.JFrame + + +class PDEWindow(titleKey: String = "", content: @Composable () -> Unit): JFrame(){ + init{ + val mac = SystemInfo.isMacFullWindowContentSupported + + rootPane.apply{ + putClientProperty("apple.awt.transparentTitleBar", mac) + putClientProperty("apple.awt.fullWindowContent", mac) + } + + defaultCloseOperation = DISPOSE_ON_CLOSE + ComposePanel().apply { + setContent { + ProcessingTheme { + val locale = LocalLocale.current + this@PDEWindow.title = locale[titleKey] + + Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { + content() + } + } + } + + this@PDEWindow.add(this) + } + pack() + background = java.awt.Color.white + setLocationRelativeTo(null) + addKeyListener(object : KeyAdapter() { + override fun keyPressed(e: KeyEvent) { + if (e.keyCode == KeyEvent.VK_ESCAPE) this@PDEWindow.dispose() + } + }) + isResizable = false + isVisible = true + requestFocus() + } +} + +fun pdeapplication(titleKey: String = "",content: @Composable () -> Unit){ + application { + val windowState = rememberWindowState( + size = DpSize.Unspecified, + position = WindowPosition(Alignment.Center) + ) + ProcessingTheme { + val locale = LocalLocale.current + val mac = SystemInfo.isMacFullWindowContentSupported + Window(onCloseRequest = ::exitApplication, state = windowState, title = locale[titleKey]) { + window.rootPane.apply { + putClientProperty("apple.awt.fullWindowContent", mac) + putClientProperty("apple.awt.transparentTitleBar", mac) + } + Surface(color = colors.background) { + Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { + content() + } + } + } + } + } +} \ No newline at end of file From 5c020dd0170153bd1a31ffaeed853966e66c5175 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 7 Feb 2025 15:49:25 +0100 Subject: [PATCH 03/50] Welcome Screen: Initial Layout --- app/src/main/resources/welcome/intro/long.svg | 7 + .../main/resources/welcome/intro/short.svg | 17 +++ app/src/processing/app/ui/Welcome.kt | 144 +++++++++++++++++- app/src/processing/app/ui/WelcomeToBeta.kt | 17 +-- app/src/processing/app/ui/theme/Typography.kt | 41 +++-- build/shared/lib/fonts/SpaceGrotesk-Bold.ttf | Bin 0 -> 86520 bytes .../shared/lib/fonts/SpaceGrotesk-LICENSE.txt | 93 +++++++++++ build/shared/lib/fonts/SpaceGrotesk-Light.ttf | Bin 0 -> 86616 bytes .../shared/lib/fonts/SpaceGrotesk-Medium.ttf | Bin 0 -> 86616 bytes .../shared/lib/fonts/SpaceGrotesk-Regular.ttf | Bin 0 -> 86592 bytes .../lib/fonts/SpaceGrotesk-SemiBold.ttf | Bin 0 -> 86576 bytes build/shared/lib/languages/PDE.properties | 10 ++ 12 files changed, 302 insertions(+), 27 deletions(-) create mode 100644 app/src/main/resources/welcome/intro/long.svg create mode 100644 app/src/main/resources/welcome/intro/short.svg create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Bold.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-LICENSE.txt create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Light.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Medium.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Regular.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-SemiBold.ttf diff --git a/app/src/main/resources/welcome/intro/long.svg b/app/src/main/resources/welcome/intro/long.svg new file mode 100644 index 0000000000..19fba54269 --- /dev/null +++ b/app/src/main/resources/welcome/intro/long.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/resources/welcome/intro/short.svg b/app/src/main/resources/welcome/intro/short.svg new file mode 100644 index 0000000000..d08759c01c --- /dev/null +++ b/app/src/main/resources/welcome/intro/short.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 8743282d62..698f2bb1ea 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -1,11 +1,27 @@ package processing.app.ui -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.material.MaterialTheme.typography +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import processing.app.Base +import processing.app.ui.theme.LocalLocale +import processing.app.ui.theme.PDEButton import processing.app.ui.theme.PDEWindow import processing.app.ui.theme.pdeapplication import java.io.IOException @@ -22,7 +38,129 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { companion object { @Composable fun welcome() { - Box(modifier = Modifier.sizeIn(815.dp, 450.dp)){ + Row( + modifier = Modifier +// .background( +// Brush.linearGradient( +// colorStops = arrayOf(0.0f to Color.Transparent, 1f to Color.Blue), +// start = Offset(815f / 2, 0f), +// end = Offset(815f, 450f) +// ) +// ) + .size(815.dp, 450.dp) + .padding(32.dp) + + , + horizontalArrangement = Arrangement.spacedBy(32.dp) + ){ + Box(modifier = Modifier + .weight(1f) + .fillMaxHeight() + ){ + intro() + } + Column(modifier = Modifier + .weight(1.25f) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween + ){ + examples() + val locale = LocalLocale.current + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = locale["welcome.action.examples"], + ) + Text( + text = locale["welcome.action.tutorials"], + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ){ + Text( + text = locale["welcome.action.startup"], + ) + PDEButton(onClick = { println("Open") }) { + val locale = LocalLocale.current + Text(locale["welcome.action.go"]) + } + } + } + } + } + + @Composable + fun intro(){ + val locale = LocalLocale.current + Column( + modifier = Modifier + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween) { + Column { + Text( + text = locale["welcome.intro.title"], + style = typography.h4, + ) + Text( + text = locale["welcome.intro.message"], + style = typography.body1, + ) + } + Column { + Text( + text = locale["welcome.intro.suggestion"], + style = typography.body1, + modifier = Modifier + .padding(vertical = 16.dp) + .clip(RoundedCornerShape(12.dp)) + .background(colors.primary) + .padding(16.dp) + .sizeIn(maxWidth = 200.dp) + + ) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Image( + painter = painterResource("welcome/intro/long.svg"), + contentDescription = locale["welcome.intro.long"], + modifier = Modifier + .offset(x = -32.dp) + ) + Image( + painter = painterResource("welcome/intro/short.svg"), + contentDescription = locale["welcome.intro.short"], + modifier = Modifier + .align(Alignment.Bottom) + ) + } + } + } + } + @Composable + fun examples(){ + LazyVerticalGrid( + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ){ + items(4){ + Column { + Box( + modifier = Modifier + .background(colors.primary) + .width(185.dp) + .aspectRatio(16f / 9f) + ) + Text("Example $it") + } + } } } diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index ab0aaa9e17..ac8302fb81 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -103,21 +103,8 @@ class WelcomeToBeta { @JvmStatic fun main(args: Array) { - application { - val windowState = rememberWindowState( - size = DpSize.Unspecified, - position = WindowPosition(Alignment.Center) - ) - - Window(onCloseRequest = ::exitApplication, state = windowState, title = Locale()["beta.window.title"]) { - ProcessingTheme { - Surface(color = colors.background) { - welcomeToBeta { - exitApplication() - } - } - } - } + pdeapplication("beta.window.title") { + welcomeToBeta() } } } diff --git a/app/src/processing/app/ui/theme/Typography.kt b/app/src/processing/app/ui/theme/Typography.kt index 5d87c490e6..c21d554f7e 100644 --- a/app/src/processing/app/ui/theme/Typography.kt +++ b/app/src/processing/app/ui/theme/Typography.kt @@ -2,6 +2,8 @@ package processing.app.ui.theme import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle @@ -21,18 +23,39 @@ val processingFont = FontFamily( style = FontStyle.Normal ) ) +val spaceGroteskFont = FontFamily( + Font( + resource = "SpaceGrotesk-Bold.ttf", + weight = FontWeight.Bold, + ), + Font( + resource = "SpaceGrotesk-Regular.ttf", + weight = FontWeight.Normal, + ), + Font( + resource = "SpaceGrotesk-Medium.ttf", + weight = FontWeight.Medium, + ), + Font( + resource = "SpaceGrotesk-SemiBold.ttf", + weight = FontWeight.SemiBold, + ), + Font( + resource = "SpaceGrotesk-Light.ttf", + weight = FontWeight.Light, + ) +) val Typography = Typography( + defaultFontFamily = spaceGroteskFont, + h4 = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 19.sp, + lineHeight = 24.sp + ), body1 = TextStyle( - fontFamily = processingFont, fontWeight = FontWeight.Normal, - fontSize = 13.sp, - lineHeight = 16.sp + fontSize = 15.sp, + lineHeight = 19.sp ), - subtitle1 = TextStyle( - fontFamily = processingFont, - fontWeight = FontWeight.Bold, - fontSize = 16.sp, - lineHeight = 20.sp - ) ) \ No newline at end of file diff --git a/build/shared/lib/fonts/SpaceGrotesk-Bold.ttf b/build/shared/lib/fonts/SpaceGrotesk-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0408641c61d0f3ce53d24348e9db72dcc1261179 GIT binary patch literal 86520 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 literal 0 HcmV?d00001 diff --git a/build/shared/lib/fonts/SpaceGrotesk-Regular.ttf b/build/shared/lib/fonts/SpaceGrotesk-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..981bcf5b2cb0a1ca43dcee7577015c0bfbdf91fb GIT binary patch literal 86592 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 Sp;;r@Vp7<@IS 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 literal 0 HcmV?d00001 diff --git a/build/shared/lib/fonts/SpaceGrotesk-SemiBold.ttf b/build/shared/lib/fonts/SpaceGrotesk-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7e02e51e48209e508954e9fdc08a1036d752702 GIT binary patch literal 86576 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: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 WBn3EMsDPe7sg0 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 Date: Fri, 7 Feb 2025 16:34:23 +0100 Subject: [PATCH 04/50] Welcome Screen: WIP --- .../main/resources/welcome/intro/bubble.svg | 3 + app/src/processing/app/Preferences.kt | 12 +++- app/src/processing/app/ui/Welcome.kt | 68 +++++++++++++++---- app/src/processing/app/ui/theme/Colors.kt | 20 ++++++ build/shared/lib/languages/PDE.properties | 4 +- 5 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 app/src/main/resources/welcome/intro/bubble.svg create mode 100644 app/src/processing/app/ui/theme/Colors.kt diff --git a/app/src/main/resources/welcome/intro/bubble.svg b/app/src/main/resources/welcome/intro/bubble.svg new file mode 100644 index 0000000000..a3997b1e79 --- /dev/null +++ b/app/src/main/resources/welcome/intro/bubble.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/processing/app/Preferences.kt b/app/src/processing/app/Preferences.kt index c5645c9bbc..43b55a9176 100644 --- a/app/src/processing/app/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -27,8 +27,18 @@ fun loadPreferences(): Properties{ preferencesFile.createNewFile() } watchFile(preferencesFile) + var update by remember { mutableStateOf(System.currentTimeMillis()) } - return Properties().apply { + // TODO: Make observable when preferences change + // TODO: Save to file when preferences change + class ObservableProperties : Properties() { + override fun setProperty(key: String, value: String): Any? { + update = System.currentTimeMillis() + return super.setProperty(key, value) + } + } + + return ObservableProperties().apply { load(ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME) ?: InputStream.nullInputStream()) load(preferencesFile.inputStream()) } diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 698f2bb1ea..f655992202 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -2,13 +2,16 @@ package processing.app.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Checkbox import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.MaterialTheme.typography +import androidx.compose.material.RadioButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -20,6 +23,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import processing.app.Base +import processing.app.LocalPreferences import processing.app.ui.theme.LocalLocale import processing.app.ui.theme.PDEButton import processing.app.ui.theme.PDEWindow @@ -69,21 +73,42 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { Column( verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( - text = locale["welcome.action.examples"], - ) - Text( - text = locale["welcome.action.tutorials"], - ) + chip { + Text( + text = locale["welcome.action.examples"], + ) + } + chip { + Text( + text = locale["welcome.action.tutorials"], + ) + } } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ){ - Text( - text = locale["welcome.action.startup"], - ) + verticalAlignment = Alignment.Bottom + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .offset (-32.dp) + + ) { + val preferences = LocalPreferences.current + Checkbox( + checked = preferences["welcome.four.show"]?.equals("true") ?: false, + onCheckedChange = { + preferences.setProperty("welcome.four.show",it.toString()) + }, + modifier = Modifier + .size(24.dp) + ) + Text( + text = locale["welcome.action.startup"], + ) + } PDEButton(onClick = { println("Open") }) { val locale = LocalLocale.current Text(locale["welcome.action.go"]) @@ -115,13 +140,19 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { text = locale["welcome.intro.suggestion"], style = typography.body1, modifier = Modifier - .padding(vertical = 16.dp) + .padding(top = 16.dp) .clip(RoundedCornerShape(12.dp)) .background(colors.primary) .padding(16.dp) .sizeIn(maxWidth = 200.dp) ) + Image( + painter = painterResource("welcome/intro/bubble.svg"), + contentDescription = locale["welcome.intro.long"], + modifier = Modifier + .align{ _, space, _ -> space / 4 } + ) Row( modifier = Modifier .fillMaxWidth(), @@ -165,6 +196,19 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { } } + @Composable + fun chip(content: @Composable () -> Unit){ + Box( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background(colors.surface) + .border(1.dp, colors.primary, RoundedCornerShape(12.dp)) + .padding(vertical = 4.dp, horizontal = 12.dp) + ){ + content() + } + } + @JvmStatic fun main(args: Array) { pdeapplication("menu.help.welcome") { diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt new file mode 100644 index 0000000000..91e7d1e6b7 --- /dev/null +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -0,0 +1,20 @@ +package processing.app.ui.theme + +import androidx.compose.material.Colors +import androidx.compose.ui.graphics.Color + +//val Colors = Colors( +// primary = Color("#0F195A".toColorInt()), +// primaryVariant = theme.getColor("toolbar.button.pressed.field"), +// secondary = theme.getColor("editor.gradient.bottom"), +// secondaryVariant = theme.getColor("editor.scrollbar.thumb.pressed.color"), +// background = theme.getColor("editor.bgcolor"), +// surface = theme.getColor("editor.bgcolor"), +// error = theme.getColor("status.error.bgcolor"), +// onPrimary = theme.getColor("toolbar.button.enabled.field"), +// onSecondary = theme.getColor("toolbar.button.enabled.field"), +// onBackground = theme.getColor("editor.fgcolor"), +// onSurface = theme.getColor("editor.fgcolor"), +// onError = theme.getColor("status.error.fgcolor"), +// isLight = theme.getProperty("laf.mode").equals("light") +//) \ No newline at end of file diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index 00daaa7bc5..59cd63c74b 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -621,8 +621,8 @@ welcome.intro.message = A flexible software sketchbook and a language for learni welcome.intro.suggestion = Is it your first time using Processing? Try one of the examples on the right. welcome.action.examples = More examples welcome.action.tutorials = Tutorials -welcome.action.startup = Startup -welcome.action.go = "Let's go!" +welcome.action.startup = Show this window at startup +welcome.action.go = Let's go! # --------------------------------------- # Beta From 2f12d13422c0359562663d93202fe7ce80c3c949 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 7 Feb 2025 21:53:49 +0100 Subject: [PATCH 05/50] Saveable/Reactive Preferences --- app/src/processing/app/Preferences.kt | 69 ++++++++++++------- .../app/contrib/ui/ContributionManager.kt | 9 +-- app/src/processing/app/ui/theme/Theme.kt | 2 +- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/app/src/processing/app/Preferences.kt b/app/src/processing/app/Preferences.kt index 43b55a9176..5c0751d87a 100644 --- a/app/src/processing/app/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -2,9 +2,13 @@ package processing.app import androidx.compose.runtime.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.launch import java.io.File import java.io.InputStream +import java.io.OutputStream import java.nio.file.* import java.util.Properties @@ -15,35 +19,58 @@ const val DEFAULTS_FILE_NAME = "defaults.txt" fun PlatformStart(){ Platform.inst ?: Platform.init() } +class ReactiveProperties: Properties() { + val _stateMap = mutableStateMapOf() + override fun setProperty(key: String, value: String) { + super.setProperty(key, value) + _stateMap[key] = value + } + + override fun getProperty(key: String): String? { + return _stateMap[key] ?: super.getProperty(key) + } + + operator fun get(key: String): String? = getProperty(key) +} +val LocalPreferences = compositionLocalOf { error("No preferences provided") } +@OptIn(FlowPreview::class) @Composable -fun loadPreferences(): Properties{ +fun PreferencesProvider(content: @Composable () -> Unit){ PlatformStart() val settingsFolder = Platform.getSettingsFolder() val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME) - if(!preferencesFile.exists()){ - preferencesFile.createNewFile() - } - watchFile(preferencesFile) - var update by remember { mutableStateOf(System.currentTimeMillis()) } + val update = watchFile(preferencesFile) + val properties = remember(preferencesFile, update) { ReactiveProperties().apply { + load((ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME)?: InputStream.nullInputStream()).reader(Charsets.UTF_8)) + load(preferencesFile.inputStream().reader(Charsets.UTF_8)) + }} - // TODO: Make observable when preferences change - // TODO: Save to file when preferences change - class ObservableProperties : Properties() { - override fun setProperty(key: String, value: String): Any? { - update = System.currentTimeMillis() - return super.setProperty(key, value) - } + val initialState = remember(properties) { properties._stateMap.toMap() } + + LaunchedEffect(properties) { + snapshotFlow { properties._stateMap.toMap() } + .dropWhile { it == initialState } + .debounce(1000) + .collect { + preferencesFile.outputStream().use { output -> + output.write( + properties.entries + .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.key.toString() }) + .joinToString("\n") { (key, value) -> "$key=$value" } + .toByteArray() + ) + } + } } - return ObservableProperties().apply { - load(ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME) ?: InputStream.nullInputStream()) - load(preferencesFile.inputStream()) + CompositionLocalProvider(LocalPreferences provides properties){ + content() } -} +} @Composable fun watchFile(file: File): Any? { val scope = rememberCoroutineScope() @@ -72,12 +99,4 @@ fun watchFile(file: File): Any? { } } return event -} -val LocalPreferences = compositionLocalOf { error("No preferences provided") } -@Composable -fun PreferencesProvider(content: @Composable () -> Unit){ - val preferences = loadPreferences() - CompositionLocalProvider(LocalPreferences provides preferences){ - content() - } } \ No newline at end of file diff --git a/app/src/processing/app/contrib/ui/ContributionManager.kt b/app/src/processing/app/contrib/ui/ContributionManager.kt index 2ad472159b..4d21227a4d 100644 --- a/app/src/processing/app/contrib/ui/ContributionManager.kt +++ b/app/src/processing/app/contrib/ui/ContributionManager.kt @@ -22,8 +22,9 @@ import androidx.compose.ui.window.application import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration import kotlinx.serialization.Serializable +import processing.app.LocalPreferences import processing.app.Platform -import processing.app.loadPreferences +import processing.app.ReactiveProperties import java.net.URL import java.util.* import javax.swing.JFrame @@ -106,7 +107,7 @@ fun contributionsManager(){ var localContributions by remember { mutableStateOf(listOf()) } var error by remember { mutableStateOf(null) } - val preferences = loadPreferences() + val preferences = LocalPreferences.current LaunchedEffect(preferences){ try { @@ -284,9 +285,9 @@ fun contributionsManager(){ } -fun loadContributionProperties(preferences: Properties): List>{ +fun loadContributionProperties(preferences: ReactiveProperties): List>{ val result = mutableListOf>() - val sketchBook = Path(preferences.getProperty("sketchbook.path.four", Platform.getDefaultSketchbookFolder().path)) + val sketchBook = Path(preferences.getProperty("sketchbook.path.four") ?: Platform.getDefaultSketchbookFolder().path) sketchBook.forEachDirectoryEntry{ contributionsFolder -> if(!contributionsFolder.isDirectory()) return@forEachDirectoryEntry val typeName = contributionsFolder.fileName.toString() diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 735d8e5b2a..82428db7f6 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -16,7 +16,7 @@ import java.util.Properties class Theme(themeFile: String? = "") : Properties() { init { load(ClassLoader.getSystemResourceAsStream("theme.txt")) - load(ClassLoader.getSystemResourceAsStream(themeFile) ?: InputStream.nullInputStream()) + load(ClassLoader.getSystemResourceAsStream(themeFile ?: "") ?: InputStream.nullInputStream()) } fun getColor(key: String): Color { return Color(getProperty(key).toColorInt()) From b0c61a4467e8c9f5d02bcc8c84e47908fad22d95 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sat, 8 Feb 2025 11:10:33 +0100 Subject: [PATCH 06/50] Welcome Screen: Load & Display examples --- app/src/processing/app/ui/Welcome.kt | 171 +++++++++++++++++++--- app/src/processing/app/ui/theme/Window.kt | 17 ++- 2 files changed, 161 insertions(+), 27 deletions(-) diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index f655992202..981eb059cf 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -6,35 +6,54 @@ import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Checkbox -import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.MaterialTheme.typography -import androidx.compose.material.RadioButton import androidx.compose.material.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.res.loadImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import com.formdev.flatlaf.util.SystemInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.decodeToImageBitmap import processing.app.Base import processing.app.LocalPreferences -import processing.app.ui.theme.LocalLocale -import processing.app.ui.theme.PDEButton -import processing.app.ui.theme.PDEWindow -import processing.app.ui.theme.pdeapplication +import processing.app.Messages +import processing.app.Platform +import processing.app.ui.theme.* +import java.awt.Cursor +import java.io.File import java.io.IOException +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes import javax.swing.SwingUtilities +import kotlin.io.path.exists +import kotlin.io.path.inputStream +import kotlin.io.path.isDirectory class Welcome @Throws(IOException::class) constructor(base: Base) { init { SwingUtilities.invokeLater { - PDEWindow("menu.help.welcome") { + PDEWindow("menu.help.welcome", fullWindowContent = true) { welcome() } } @@ -44,17 +63,16 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { fun welcome() { Row( modifier = Modifier -// .background( -// Brush.linearGradient( -// colorStops = arrayOf(0.0f to Color.Transparent, 1f to Color.Blue), -// start = Offset(815f / 2, 0f), -// end = Offset(815f, 450f) -// ) -// ) - .size(815.dp, 450.dp) + .background( + Brush.linearGradient( + colorStops = arrayOf(0f to Color.Transparent, 1f to Color("#C0D7FF".toColorInt())), + start = Offset(815f, 0f), + end = Offset(815f* 2, 450f) + ) + ) + .size(815.dp, 500.dp) .padding(32.dp) - - , + .padding(top = if(SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(32.dp) ){ Box(modifier = Modifier @@ -174,22 +192,129 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { } } } + + data class Example( + val folder: Path, + val library: Path, + val path: String = library.resolve("examples").relativize(folder).toString(), + val title: String = folder.fileName.toString(), + val image: Path = folder.resolve("$title.png") + ) + + @Composable + fun loadExamples(): List { + val sketchbook = rememberSketchbookPath() + val resources = File(System.getProperty("compose.application.resources.dir") ?: "") + var examples by remember { mutableStateOf(emptyList()) } + + val settingsFolder = Platform.getSettingsFolder() + val examplesCache = settingsFolder.resolve("examples.cache") + LaunchedEffect(sketchbook, resources){ + if (!examplesCache.exists()) return@LaunchedEffect + withContext(Dispatchers.IO) { + examples = examplesCache.readText().lines().map { + val (library, folder) = it.split(",") + Example( + folder = File(folder).toPath(), + library = File(library).toPath() + ) + } + } + } + + LaunchedEffect(sketchbook, resources){ + withContext(Dispatchers.IO) { + // TODO: Optimize + Messages.log("Start scanning for examples in $sketchbook and $resources") + // Folders that can contain contributions with examples + val scanned = listOf("libraries", "examples", "modes") + .flatMap { listOf(sketchbook.resolve(it), resources.resolve(it)) } + .filter { it.exists() && it.isDirectory() } + // Find contributions within those folders + .flatMap { Files.list(it.toPath()).toList() } + .filter { Files.isDirectory(it) } + // Find examples within those contributions + .flatMap { library -> + val fs = FileSystems.getDefault() + val matcher = fs.getPathMatcher("glob:**/*.pde") + val exampleFolders = mutableListOf() + val examples = library.resolve("examples") + if (!Files.exists(examples) || !examples.isDirectory()) return@flatMap emptyList() + + Files.walkFileTree(library, object : SimpleFileVisitor() { + override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + if (matcher.matches(file)) { + exampleFolders.add(file.parent) + } + return FileVisitResult.CONTINUE + } + }) + return@flatMap exampleFolders.map { folder -> + Example( + folder, + library, + ) + } + } + .filter { it.image.exists() } + Messages.log("Done scanning for examples in $sketchbook and $resources") + if(scanned.isEmpty()) return@withContext + examples = scanned + examplesCache.writeText(examples.joinToString("\n") { "${it.library},${it.folder}" }) + } + } + + return examples + + } + + @Composable + fun rememberSketchbookPath(): File { + val preferences = LocalPreferences.current + val sketchbookPath = remember(preferences["sketchbook.path.four"]) { + preferences["sketchbook.path.four"] ?: Platform.getDefaultSketchbookFolder().toString() + } + return File(sketchbookPath) + } + + + @OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class) @Composable fun examples(){ + val examples = loadExamples() + // grab 4 random ones + val randoms = examples.shuffled().take(4) + LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ){ - items(4){ - Column { - Box( + items(randoms){ example -> + Column( + modifier = Modifier + .onPointerEvent(PointerEventType.Press) { + } + .onPointerEvent(PointerEventType.Release) { + } + .onPointerEvent(PointerEventType.Enter) { + } + .onPointerEvent(PointerEventType.Exit) { + } + .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) + ) { + val imageBitmap: ImageBitmap = remember(example.image) { + example.image.inputStream().readAllBytes().decodeToImageBitmap() + } + Image( + painter = BitmapPainter(imageBitmap), + contentDescription = example.title, modifier = Modifier .background(colors.primary) .width(185.dp) .aspectRatio(16f / 9f) ) - Text("Example $it") + Text(example.title) } } @@ -211,7 +336,7 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { @JvmStatic fun main(args: Array) { - pdeapplication("menu.help.welcome") { + pdeapplication("menu.help.welcome", fullWindowContent = true) { welcome() } } diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index eafa33a236..26b45d9563 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -1,5 +1,6 @@ package processing.app.ui.theme +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme.colors @@ -8,6 +9,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window @@ -22,7 +26,7 @@ import java.awt.event.KeyEvent import javax.swing.JFrame -class PDEWindow(titleKey: String = "", content: @Composable () -> Unit): JFrame(){ +class PDEWindow(titleKey: String = "", fullWindowContent: Boolean = false, content: @Composable () -> Unit): JFrame(){ init{ val mac = SystemInfo.isMacFullWindowContentSupported @@ -38,8 +42,11 @@ class PDEWindow(titleKey: String = "", content: @Composable () -> Unit): JFrame( val locale = LocalLocale.current this@PDEWindow.title = locale[titleKey] - Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { + Box(modifier = Modifier + .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) + ) { content() + } } } @@ -60,7 +67,7 @@ class PDEWindow(titleKey: String = "", content: @Composable () -> Unit): JFrame( } } -fun pdeapplication(titleKey: String = "",content: @Composable () -> Unit){ +fun pdeapplication(titleKey: String = "", fullWindowContent: Boolean = false,content: @Composable () -> Unit){ application { val windowState = rememberWindowState( size = DpSize.Unspecified, @@ -75,7 +82,9 @@ fun pdeapplication(titleKey: String = "",content: @Composable () -> Unit){ putClientProperty("apple.awt.transparentTitleBar", mac) } Surface(color = colors.background) { - Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { + Box(modifier = Modifier + .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) + ) { content() } } From 3d81b02ffed2c28560bc2067f1584e05db0bad51 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sat, 8 Feb 2025 22:47:28 +0100 Subject: [PATCH 07/50] Welcome Screen: Language selection --- app/build.gradle.kts | 2 + app/src/processing/app/ui/Welcome.kt | 329 ++++++------------ .../app/ui/components/LanuageSelector.kt | 113 ++++++ .../app/ui/components/examples/Examples.kt | 166 +++++++++ app/src/processing/app/ui/theme/Chip.kt | 39 +++ app/src/processing/app/ui/theme/Locale.kt | 52 ++- 6 files changed, 469 insertions(+), 232 deletions(-) create mode 100644 app/src/processing/app/ui/components/LanuageSelector.kt create mode 100644 app/src/processing/app/ui/components/examples/Examples.kt create mode 100644 app/src/processing/app/ui/theme/Chip.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8dd6e88f0d..bfcb65cc07 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,6 +21,7 @@ tasks.withType { systemProperty("processing.contributions.source", "https://contributions-preview.processing.org/contribs.txt") systemProperty("processing.download.page", "https://processing.org/download/") systemProperty("processing.download.latest", "https://processing.org/download/latest.txt") + systemProperty("processing.tutorials", "https://processing.org/tutorials/") } @@ -95,6 +96,7 @@ dependencies { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) + implementation(compose.materialIconsExtended) implementation(compose.desktop.currentOs) diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 981eb059cf..5173b239cd 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -2,135 +2,90 @@ package processing.app.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Checkbox +import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.MaterialTheme.typography -import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.PointerIcon -import androidx.compose.ui.input.pointer.onPointerEvent -import androidx.compose.ui.input.pointer.pointerHoverIcon -import androidx.compose.ui.res.loadImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.formdev.flatlaf.util.SystemInfo -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.jetbrains.compose.resources.ExperimentalResourceApi -import org.jetbrains.compose.resources.decodeToImageBitmap -import processing.app.Base -import processing.app.LocalPreferences -import processing.app.Messages -import processing.app.Platform +import processing.app.* +import processing.app.ui.components.LanguageChip +import processing.app.ui.components.examples.examples import processing.app.ui.theme.* -import java.awt.Cursor -import java.io.File +import java.awt.Desktop import java.io.IOException +import java.net.URI import java.nio.file.* -import java.nio.file.attribute.BasicFileAttributes +import java.util.* import javax.swing.SwingUtilities -import kotlin.io.path.exists -import kotlin.io.path.inputStream -import kotlin.io.path.isDirectory + class Welcome @Throws(IOException::class) constructor(base: Base) { init { SwingUtilities.invokeLater { PDEWindow("menu.help.welcome", fullWindowContent = true) { - welcome() + CompositionLocalProvider(LocalBase provides base) { + welcome() + } } } } + companion object { + val LocalBase = compositionLocalOf { null } @Composable fun welcome() { - Row( + Column ( modifier = Modifier .background( Brush.linearGradient( colorStops = arrayOf(0f to Color.Transparent, 1f to Color("#C0D7FF".toColorInt())), start = Offset(815f, 0f), - end = Offset(815f* 2, 450f) + end = Offset(815f * 2, 450f) ) ) - .size(815.dp, 500.dp) - .padding(32.dp) - .padding(top = if(SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp), - horizontalArrangement = Arrangement.spacedBy(32.dp) + .padding(horizontal = 32.dp) + .padding(bottom = 32.dp) + .padding(top = if (SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp), ){ - Box(modifier = Modifier - .weight(1f) - .fillMaxHeight() - ){ - intro() + Column( + horizontalAlignment = Alignment.End, + modifier = Modifier + .align(Alignment.End) + ) { + LanguageChip() } - Column(modifier = Modifier - .weight(1.25f) - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ){ - examples() - val locale = LocalLocale.current - Column( - verticalArrangement = Arrangement.spacedBy(16.dp) + Row( + horizontalArrangement = Arrangement.spacedBy(32.dp), + modifier = Modifier + .size(815.dp, 450.dp) + ) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() ) { - chip { - Text( - text = locale["welcome.action.examples"], - ) - } - chip { - Text( - text = locale["welcome.action.tutorials"], - ) - } + intro() } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom + Column( + modifier = Modifier + .weight(1.25f) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .offset (-32.dp) - - ) { - val preferences = LocalPreferences.current - Checkbox( - checked = preferences["welcome.four.show"]?.equals("true") ?: false, - onCheckedChange = { - preferences.setProperty("welcome.four.show",it.toString()) - }, - modifier = Modifier - .size(24.dp) - ) - Text( - text = locale["welcome.action.startup"], - ) - } - PDEButton(onClick = { println("Open") }) { - val locale = LocalLocale.current - Text(locale["welcome.action.go"]) - } + examples() + actions() } } } @@ -193,146 +148,82 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { } } - data class Example( - val folder: Path, - val library: Path, - val path: String = library.resolve("examples").relativize(folder).toString(), - val title: String = folder.fileName.toString(), - val image: Path = folder.resolve("$title.png") - ) - @Composable - fun loadExamples(): List { - val sketchbook = rememberSketchbookPath() - val resources = File(System.getProperty("compose.application.resources.dir") ?: "") - var examples by remember { mutableStateOf(emptyList()) } - - val settingsFolder = Platform.getSettingsFolder() - val examplesCache = settingsFolder.resolve("examples.cache") - LaunchedEffect(sketchbook, resources){ - if (!examplesCache.exists()) return@LaunchedEffect - withContext(Dispatchers.IO) { - examples = examplesCache.readText().lines().map { - val (library, folder) = it.split(",") - Example( - folder = File(folder).toPath(), - library = File(library).toPath() - ) - } + fun actions(){ + val locale = LocalLocale.current + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + val base = LocalBase.current + PDEChip(onClick = { + base?.defaultMode?.showExamplesFrame() + }) { + Text( + text = locale["welcome.action.examples"], + ) + Image( + imageVector = Icons.AutoMirrored.Default.ArrowForward, + contentDescription = locale["welcome.action.tutorials"], + modifier = Modifier + .padding(start = 8.dp) + .size(typography.body1.fontSize.value.dp) + ) } - } - - LaunchedEffect(sketchbook, resources){ - withContext(Dispatchers.IO) { - // TODO: Optimize - Messages.log("Start scanning for examples in $sketchbook and $resources") - // Folders that can contain contributions with examples - val scanned = listOf("libraries", "examples", "modes") - .flatMap { listOf(sketchbook.resolve(it), resources.resolve(it)) } - .filter { it.exists() && it.isDirectory() } - // Find contributions within those folders - .flatMap { Files.list(it.toPath()).toList() } - .filter { Files.isDirectory(it) } - // Find examples within those contributions - .flatMap { library -> - val fs = FileSystems.getDefault() - val matcher = fs.getPathMatcher("glob:**/*.pde") - val exampleFolders = mutableListOf() - val examples = library.resolve("examples") - if (!Files.exists(examples) || !examples.isDirectory()) return@flatMap emptyList() - - Files.walkFileTree(library, object : SimpleFileVisitor() { - override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { - if (matcher.matches(file)) { - exampleFolders.add(file.parent) - } - return FileVisitResult.CONTINUE - } - }) - return@flatMap exampleFolders.map { folder -> - Example( - folder, - library, - ) - } - } - .filter { it.image.exists() } - Messages.log("Done scanning for examples in $sketchbook and $resources") - if(scanned.isEmpty()) return@withContext - examples = scanned - examplesCache.writeText(examples.joinToString("\n") { "${it.library},${it.folder}" }) + PDEChip(onClick = { + if (!Desktop.isDesktopSupported()) return@PDEChip + val desktop = Desktop.getDesktop() + if(!desktop.isSupported(Desktop.Action.BROWSE)) return@PDEChip + try { + desktop.browse(URI(System.getProperty("processing.tutorials"))) + } catch (e: Exception) { + e.printStackTrace() + } + }) { + Text( + text = locale["welcome.action.tutorials"], + ) + Image( + imageVector = Icons.AutoMirrored.Default.ArrowForward, + contentDescription = locale["welcome.action.tutorials"], + modifier = Modifier + .padding(start = 8.dp) + .size(typography.body1.fontSize.value.dp) + ) } } - - return examples - - } - - @Composable - fun rememberSketchbookPath(): File { - val preferences = LocalPreferences.current - val sketchbookPath = remember(preferences["sketchbook.path.four"]) { - preferences["sketchbook.path.four"] ?: Platform.getDefaultSketchbookFolder().toString() - } - return File(sketchbookPath) - } - - - @OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class) - @Composable - fun examples(){ - val examples = loadExamples() - // grab 4 random ones - val randoms = examples.shuffled().take(4) - - LazyVerticalGrid( - columns = GridCells.Fixed(2), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - ){ - items(randoms){ example -> - Column( + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .offset(-32.dp) + + ) { + val preferences = LocalPreferences.current + Checkbox( + checked = preferences["welcome.four.show"]?.equals("true") ?: false, + onCheckedChange = { + preferences.setProperty("welcome.four.show", it.toString()) + }, modifier = Modifier - .onPointerEvent(PointerEventType.Press) { - } - .onPointerEvent(PointerEventType.Release) { - } - .onPointerEvent(PointerEventType.Enter) { - } - .onPointerEvent(PointerEventType.Exit) { - } - .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) - ) { - val imageBitmap: ImageBitmap = remember(example.image) { - example.image.inputStream().readAllBytes().decodeToImageBitmap() - } - Image( - painter = BitmapPainter(imageBitmap), - contentDescription = example.title, - modifier = Modifier - .background(colors.primary) - .width(185.dp) - .aspectRatio(16f / 9f) - ) - Text(example.title) - } + .size(24.dp) + ) + Text( + text = locale["welcome.action.startup"], + ) + } + PDEButton(onClick = { println("Open") }) { + val locale = LocalLocale.current + Text(locale["welcome.action.go"]) } - } } - @Composable - fun chip(content: @Composable () -> Unit){ - Box( - modifier = Modifier - .clip(RoundedCornerShape(12.dp)) - .background(colors.surface) - .border(1.dp, colors.primary, RoundedCornerShape(12.dp)) - .padding(vertical = 4.dp, horizontal = 12.dp) - ){ - content() - } - } + @JvmStatic fun main(args: Array) { diff --git a/app/src/processing/app/ui/components/LanuageSelector.kt b/app/src/processing/app/ui/components/LanuageSelector.kt new file mode 100644 index 0000000000..532e02d66f --- /dev/null +++ b/app/src/processing/app/ui/components/LanuageSelector.kt @@ -0,0 +1,113 @@ +package processing.app.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.MaterialTheme.typography +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Language +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import processing.app.Platform +import processing.app.ui.theme.LocalLocale +import processing.app.ui.theme.PDEChip +import processing.app.watchFile +import java.io.File +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* +import kotlin.io.path.inputStream + +data class Language( + val name: String, + val code: String, + val locale: Locale, + val properties: Properties +) + +@Composable +fun LanguageChip(){ + var expanded by remember { mutableStateOf(false) } + + val settingsFolder = Platform.getSettingsFolder() + val languageFile = File(settingsFolder, "language.txt") + watchFile(languageFile) + + val main = ClassLoader.getSystemResource("PDE.properties")?: return + + + + + val languages = remember { + val list = when(main.protocol){ + "file" -> { + val path = Paths.get(main.toURI()) + Files.list(path.parent) + } + "jar" -> { + val uri = main.toURI() + val fs = FileSystems.newFileSystem(uri, emptyMap()) + Files.list(fs.getPath("/")) + } + else -> null + } ?: return@remember null + + list.toList() + .map { Pair(it, it.fileName.toString()) } + .filter { (_, fileName) -> fileName.startsWith("PDE_") && fileName.endsWith(".properties") } + .map { (path, _) -> + path.inputStream().reader(Charsets.UTF_8).use { + val properties = Properties() + properties.load(it) + + val code = path.fileName.toString().removeSuffix(".properties").replace("PDE_", "") + val locale = Locale.forLanguageTag(code) + val name = locale.getDisplayName(locale) + + return@map Language( + name, + code, + locale, + properties + ) + } + } + } ?: return + + val current = languageFile.readText(Charsets.UTF_8).substring(0, 2) + val currentLanguage = remember(current) { languages.find { it.code.startsWith(current) } ?: languages.first()} + + val locale = LocalLocale.current + + PDEChip(onClick = { expanded = !expanded }, leadingIcon = { + Image( + imageVector = Icons.Outlined.Language, + contentDescription = "Language", + modifier = Modifier + .padding(start = 8.dp) + .size(typography.body1.fontSize.value.dp) + ) + }) { + Text(currentLanguage.name) + DropdownMenu( + expanded = expanded, + onDismissRequest = { + expanded = false + }, + ){ + for (language in languages){ + DropdownMenuItem(onClick = { + locale.set(language.locale) + expanded = false + }) { + Text(language.name) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/components/examples/Examples.kt b/app/src/processing/app/ui/components/examples/Examples.kt new file mode 100644 index 0000000000..1c08dc6683 --- /dev/null +++ b/app/src/processing/app/ui/components/examples/Examples.kt @@ -0,0 +1,166 @@ +package processing.app.ui.components.examples + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.decodeToImageBitmap +import processing.app.LocalPreferences +import processing.app.Messages +import processing.app.Platform +import java.awt.Cursor +import java.io.File +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes +import kotlin.io.path.exists +import kotlin.io.path.inputStream +import kotlin.io.path.isDirectory + +data class Example( + val folder: Path, + val library: Path, + val path: String = library.resolve("examples").relativize(folder).toString(), + val title: String = folder.fileName.toString(), + val image: Path = folder.resolve("$title.png") +) + +@Composable +fun loadExamples(): List { + val sketchbook = rememberSketchbookPath() + val resources = File(System.getProperty("compose.application.resources.dir") ?: "") + var examples by remember { mutableStateOf(emptyList()) } + + val settingsFolder = Platform.getSettingsFolder() + val examplesCache = settingsFolder.resolve("examples.cache") + LaunchedEffect(sketchbook, resources){ + if (!examplesCache.exists()) return@LaunchedEffect + withContext(Dispatchers.IO) { + examples = examplesCache.readText().lines().map { + val (library, folder) = it.split(",") + Example( + folder = File(folder).toPath(), + library = File(library).toPath() + ) + } + } + } + + LaunchedEffect(sketchbook, resources){ + withContext(Dispatchers.IO) { + // TODO: Optimize + Messages.log("Start scanning for examples in $sketchbook and $resources") + // Folders that can contain contributions with examples + val scanned = listOf("libraries", "examples", "modes") + .flatMap { listOf(sketchbook.resolve(it), resources.resolve(it)) } + .filter { it.exists() && it.isDirectory() } + // Find contributions within those folders + .flatMap { Files.list(it.toPath()).toList() } + .filter { Files.isDirectory(it) } + // Find examples within those contributions + .flatMap { library -> + val fs = FileSystems.getDefault() + val matcher = fs.getPathMatcher("glob:**/*.pde") + val exampleFolders = mutableListOf() + val examples = library.resolve("examples") + if (!Files.exists(examples) || !examples.isDirectory()) return@flatMap emptyList() + + Files.walkFileTree(library, object : SimpleFileVisitor() { + override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + if (matcher.matches(file)) { + exampleFolders.add(file.parent) + } + return FileVisitResult.CONTINUE + } + }) + return@flatMap exampleFolders.map { folder -> + Example( + folder, + library, + ) + } + } + .filter { it.image.exists() } + Messages.log("Done scanning for examples in $sketchbook and $resources") + if(scanned.isEmpty()) return@withContext + examples = scanned + examplesCache.writeText(examples.joinToString("\n") { "${it.library},${it.folder}" }) + } + } + + return examples + +} + +@Composable +fun rememberSketchbookPath(): File { + val preferences = LocalPreferences.current + val sketchbookPath = remember(preferences["sketchbook.path.four"]) { + preferences["sketchbook.path.four"] ?: Platform.getDefaultSketchbookFolder().toString() + } + return File(sketchbookPath) +} + + +@OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class) +@Composable +fun examples(){ + val examples = loadExamples() + // grab 4 random ones + val randoms = examples.shuffled().take(4) + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ){ + items(randoms){ example -> + Column( + modifier = Modifier + .onPointerEvent(PointerEventType.Press) { + } + .onPointerEvent(PointerEventType.Release) { + } + .onPointerEvent(PointerEventType.Enter) { + } + .onPointerEvent(PointerEventType.Exit) { + } + .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) + ) { + val imageBitmap: ImageBitmap = remember(example.image) { + example.image.inputStream().readAllBytes().decodeToImageBitmap() + } + Image( + painter = BitmapPainter(imageBitmap), + contentDescription = example.title, + modifier = Modifier + .background(colors.primary) + .width(185.dp) + .aspectRatio(16f / 9f) + ) + Text(example.title) + } + } + + } +} + diff --git a/app/src/processing/app/ui/theme/Chip.kt b/app/src/processing/app/ui/theme/Chip.kt new file mode 100644 index 0000000000..6d44bc512a --- /dev/null +++ b/app/src/processing/app/ui/theme/Chip.kt @@ -0,0 +1,39 @@ +package processing.app.ui.theme + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Chip +import androidx.compose.material.ChipColors +import androidx.compose.material.ChipDefaults +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun PDEChip( + onClick: () -> Unit = {}, + leadingIcon: @Composable (() -> Unit)? = null, + content: @Composable RowScope.() -> Unit +){ + Chip( + onClick = onClick, + border = BorderStroke(1.dp, colors.onSurface.copy(alpha = 0.12f)), + colors = ChipDefaults.chipColors( + backgroundColor = Color.Transparent, + contentColor = colors.onSurface + ), + leadingIcon = leadingIcon, + modifier = Modifier, + content = content + ) +} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 254c0946c1..6490c0eb90 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -1,8 +1,8 @@ package processing.app.ui.theme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection import processing.app.LocalPreferences import processing.app.Messages import processing.app.Platform @@ -12,13 +12,20 @@ import java.io.File import java.io.InputStream import java.util.* -class Locale(language: String = "") : Properties() { +class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : Properties() { + var locale: java.util.Locale = java.util.Locale.getDefault() + init { - val locale = java.util.Locale.getDefault() - load(ClassLoader.getSystemResourceAsStream("PDE.properties")) - load(ClassLoader.getSystemResourceAsStream("PDE_${locale.language}.properties") ?: InputStream.nullInputStream()) - load(ClassLoader.getSystemResourceAsStream("PDE_${locale.toLanguageTag()}.properties") ?: InputStream.nullInputStream()) - load(ClassLoader.getSystemResourceAsStream("PDE_${language}.properties") ?: InputStream.nullInputStream()) + fun loadResourceUTF8(path: String) { + val stream = ClassLoader.getSystemResourceAsStream(path) + stream?.reader(charset = Charsets.UTF_8)?.use { reader -> + load(reader) + } + } + loadResourceUTF8("PDE.properties") + loadResourceUTF8("PDE_${locale.language}.properties") + loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties") + loadResourceUTF8("PDE_${language}.properties") } @Deprecated("Use get instead", ReplaceWith("get(key)")) @@ -28,8 +35,11 @@ class Locale(language: String = "") : Properties() { return value } operator fun get(key: String): String = getProperty(key, key) + fun set(locale: java.util.Locale) { + setLocale(locale) + } } -val LocalLocale = compositionLocalOf { Locale() } +val LocalLocale = compositionLocalOf { error("No Locale Set") } @Composable fun LocaleProvider(content: @Composable () -> Unit) { PlatformStart() @@ -37,9 +47,25 @@ fun LocaleProvider(content: @Composable () -> Unit) { val settingsFolder = Platform.getSettingsFolder() val languageFile = File(settingsFolder, "language.txt") watchFile(languageFile) + var code by remember{ mutableStateOf(languageFile.readText().substring(0, 2)) } + + fun setLocale(locale: java.util.Locale) { + java.util.Locale.setDefault(locale) + languageFile.writeText(locale.language) + code = locale.language + } + + + val locale = Locale(code, ::setLocale) + Messages.log("Locale: $code") + val dir = when(locale["locale.direction"]) { + "rtl" -> LayoutDirection.Rtl + else -> LayoutDirection.Ltr + } - val locale = Locale(languageFile.readText().substring(0, 2)) - CompositionLocalProvider(LocalLocale provides locale) { - content() + CompositionLocalProvider(LocalLayoutDirection provides dir) { + CompositionLocalProvider(LocalLocale provides locale) { + content() + } } } \ No newline at end of file From cb184fb104ac9c4ead2e6352573f726189da7b70 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Feb 2025 12:19:33 +0100 Subject: [PATCH 08/50] Welcome Screen: Layout fixes --- app/src/main/resources/default.png | Bin 0 -> 1900 bytes app/src/main/resources/welcome/intro/long.svg | 14 +- app/src/main/resources/welcome/intro/wavy.svg | 7 + app/src/processing/app/Language.java | 5 + app/src/processing/app/ui/Welcome.kt | 156 ++++++++++-------- .../app/ui/components/LanuageSelector.kt | 12 ++ .../app/ui/components/examples/Examples.kt | 102 +++++++----- app/src/processing/app/ui/theme/Button.kt | 35 ++-- app/src/processing/app/ui/theme/Chip.kt | 14 +- app/src/processing/app/ui/theme/Colors.kt | 43 +++-- app/src/processing/app/ui/theme/Locale.kt | 7 +- app/src/processing/app/ui/theme/Theme.kt | 47 ++---- app/src/processing/app/ui/theme/Window.kt | 35 +++- 13 files changed, 278 insertions(+), 199 deletions(-) create mode 100644 app/src/main/resources/default.png create mode 100644 app/src/main/resources/welcome/intro/wavy.svg diff --git a/app/src/main/resources/default.png b/app/src/main/resources/default.png new file mode 100644 index 0000000000000000000000000000000000000000..df13f36105bc2af722ba3147ae97dac2e0ed6548 GIT binary patch literal 1900 zcmd^A`B&0e6#w!K5>uyeWiZoYV``b}30oBzEgP3(mb(F_rDbYl7$f-AtW1-#nd)ev z*^Fg5ZduwwB97rwI@(0yl7L!DYJj+*A~fGQGk?X*5AS{M{oHfkJ@0ehJufdPaG${% z+!_D?4EFna2Lk|t0NeHG)$l&{X&wWE?kV4hQ~=O3`gnC6b z8SfR~1psvgdMhy~0MJ3~_x3tON617k6O9jJn@7sLF(ytv`gD7w5{%5fXoruaPaySn z?fSai55xb#!TV9rboO)W@P`*o#X%U8S*4?Y|93C1m%NrkHHz8hb-cJ&g_G?&;>U|4 z1QTb*^$5g7Jwh;uzGss~i2RShwu8f&b8`~{xd}dL@I6;pOC}$SkMA5BiXM2H6m^b@ zA!u=uri>}l$RBKXQ7wjVu`BHcm!V4Iwb+JYDbcD%m~h`EesE6ekh1aq+CP(It{S&GEeD-uH} z7q=H!`dkY|UdtqUPrtryi@Lpzp#9c~)cBd??0|?nvcZEHPU1m_+w7BBMQhw(%DRcU z^zQJ3mua%d7iSgX7FA`*1|`iH(ykoe-1Sy?W@pI~7;CJ|pU%*({5Zm3EPSyybBwl& zdU`TqZa}4w6vDOB6!Mz!7^z>O<4xL>(B$F9ES9J`>e;eKeIx9j)~+li>#Or*DN05X z5gRo=n!U5UnHA!HR!7#Kiy3)mPp$2rWsG;-3Khh3YUGCPOIZ&BOEw4ST5Z7)+N}4|xbAy$Fr@|a zEtZ~;gL+^=_QBlp{QQLA0SKr5R2ij;dYxxv`zFW0d|6v{9Ed%OevcvG@){b@oqe@r zwU*gW<3WLUc1JLz_&a7NXA<(=Rz>6it~WL_sjWO-=2sGYpC_N)0{S%Rts7l1%{Tdn zp6g=nUEQdxFsaMW<@b?hXJjW5ggfO;l?b zlRT5)x7!m<8$!nX-qq#*F@bM)O4?m=zXCr6)ww^{bqa~M0XwXd%2}7bu{g25yL<}j zRi9%gq_-C_;ZoLd0kNi~p7iPAx1}i@x!I_5Z+Ss|S9iB5+&2fe?gJD7jkivj?*_7S zc}G=p5o!``)yYaU?mr`P+RLO~KEp1nPLmCWP;BI&AdCgFJx&{zHA{O+*RF75`ZS}) zg@F$Q^!I&J&F5C3LanprLau;p??*V42ed*T=viH@4%s){{?&E^HE&N_Wk$sT{|pi0 z7#8dxl;Kc9I9kR%urqxw;am(n33m@vp#sxOQ&_GtEhQZEt;V|yz%QFEg7Z_08ID7YJ3Kvq~Nv^f3CLX32OmAaMTb#?uY;01+*OxXqtJt>}|!hIQ(Sg1JU z3r=Zte($F-U*}xlamRU`wOWoUop0<;%Cl#(Zo^bvY&!?RT$(vrCNUr5kdaPLAGhMj z(cSa0(f$taX-E zA>R4!w#62_DfY - - - - - - \ No newline at end of file + + + + + + + diff --git a/app/src/main/resources/welcome/intro/wavy.svg b/app/src/main/resources/welcome/intro/wavy.svg new file mode 100644 index 0000000000..b244066fa1 --- /dev/null +++ b/app/src/main/resources/welcome/intro/wavy.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java index d55c8b710c..f0fdab8759 100644 --- a/app/src/processing/app/Language.java +++ b/app/src/processing/app/Language.java @@ -183,6 +183,11 @@ static public Language init() { return instance; } + static public void reload(){ + synchronized (Language.class) { + instance = new Language(); + } + } static private String get(String key) { LanguageBundle bundle = init().bundle; diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 5173b239cd..754c9e4fc6 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -2,6 +2,7 @@ package processing.app.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -16,8 +17,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.min import com.formdev.flatlaf.util.SystemInfo import processing.app.* import processing.app.ui.components.LanguageChip @@ -46,7 +49,7 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { val LocalBase = compositionLocalOf { null } @Composable fun welcome() { - Column ( + Column( modifier = Modifier .background( Brush.linearGradient( @@ -57,8 +60,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { ) .padding(horizontal = 32.dp) .padding(bottom = 32.dp) - .padding(top = if (SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp), - ){ + .padding(top = if (SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp) + .height(IntrinsicSize.Max) + .width(IntrinsicSize.Max) + ) { Column( horizontalAlignment = Alignment.End, modifier = Modifier @@ -67,25 +72,25 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { LanguageChip() } Row( - horizontalArrangement = Arrangement.spacedBy(32.dp), - modifier = Modifier - .size(815.dp, 450.dp) + horizontalArrangement = Arrangement.spacedBy(48.dp), ) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - ) { + Column { intro() } - Column( - modifier = Modifier - .weight(1.25f) - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween - ) { - examples() - actions() + Box{ + Column { + examples() + actions() + } + val locale = LocalLocale.current + Image( + painter = painterResource("welcome/intro/wavy.svg"), + contentDescription = locale["welcome.intro.long"], + modifier = Modifier + .height(200.dp) + .offset (32.dp) + .align(Alignment.BottomEnd) + ) } } } @@ -95,30 +100,40 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { fun intro(){ val locale = LocalLocale.current Column( + verticalArrangement = Arrangement.SpaceBetween, modifier = Modifier - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceBetween) { + .fillMaxHeight() + .width(IntrinsicSize.Max) + ) { Column { Text( text = locale["welcome.intro.title"], style = typography.h4, + modifier = Modifier + .sizeIn(maxWidth = 305.dp) ) Text( text = locale["welcome.intro.message"], style = typography.body1, + modifier = Modifier + .sizeIn(maxWidth = 305.dp) ) } - Column { + Column( + modifier = Modifier + .offset(y = 32.dp) + ){ Text( text = locale["welcome.intro.suggestion"], style = typography.body1, + color = colors.onPrimary, modifier = Modifier .padding(top = 16.dp) .clip(RoundedCornerShape(12.dp)) .background(colors.primary) - .padding(16.dp) + .padding(horizontal = 24.dp) + .padding(top = 16.dp, bottom = 24.dp) .sizeIn(maxWidth = 200.dp) - ) Image( painter = painterResource("welcome/intro/bubble.svg"), @@ -127,9 +142,9 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { .align{ _, space, _ -> space / 4 } ) Row( - modifier = Modifier - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier. + fillMaxWidth() ) { Image( painter = painterResource("welcome/intro/long.svg"), @@ -142,6 +157,7 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { contentDescription = locale["welcome.intro.short"], modifier = Modifier .align(Alignment.Bottom) + .offset(x = 16.dp, y = -16.dp) ) } } @@ -151,57 +167,55 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { @Composable fun actions(){ val locale = LocalLocale.current - Column( - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - val base = LocalBase.current - PDEChip(onClick = { - base?.defaultMode?.showExamplesFrame() - }) { - Text( - text = locale["welcome.action.examples"], - ) - Image( - imageVector = Icons.AutoMirrored.Default.ArrowForward, - contentDescription = locale["welcome.action.tutorials"], - modifier = Modifier - .padding(start = 8.dp) - .size(typography.body1.fontSize.value.dp) - ) - } - PDEChip(onClick = { - if (!Desktop.isDesktopSupported()) return@PDEChip - val desktop = Desktop.getDesktop() - if(!desktop.isSupported(Desktop.Action.BROWSE)) return@PDEChip - try { - desktop.browse(URI(System.getProperty("processing.tutorials"))) - } catch (e: Exception) { - e.printStackTrace() - } - }) { - Text( - text = locale["welcome.action.tutorials"], - ) - Image( - imageVector = Icons.AutoMirrored.Default.ArrowForward, - contentDescription = locale["welcome.action.tutorials"], - modifier = Modifier - .padding(start = 8.dp) - .size(typography.body1.fontSize.value.dp) - ) + val base = LocalBase.current + PDEChip(onClick = { + base?.defaultMode?.showExamplesFrame() + }) { + Text( + text = locale["welcome.action.examples"], + ) + Image( + imageVector = Icons.AutoMirrored.Default.ArrowForward, + contentDescription = locale["welcome.action.tutorials"], + colorFilter = ColorFilter.tint(color = LocalContentColor.current), + modifier = Modifier + .padding(start = 8.dp) + .size(typography.body1.fontSize.value.dp) + ) + } + PDEChip(onClick = { + if (!Desktop.isDesktopSupported()) return@PDEChip + val desktop = Desktop.getDesktop() + if(!desktop.isSupported(Desktop.Action.BROWSE)) return@PDEChip + try { + desktop.browse(URI(System.getProperty("processing.tutorials"))) + } catch (e: Exception) { + e.printStackTrace() } + }) { + Text( + text = locale["welcome.action.tutorials"], + ) + Image( + imageVector = Icons.AutoMirrored.Default.ArrowForward, + contentDescription = locale["welcome.action.tutorials"], + colorFilter = ColorFilter.tint(color = LocalContentColor.current), + modifier = Modifier + .padding(start = 8.dp) + .size(typography.body1.fontSize.value.dp) + ) } Row( - modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() ) { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .offset(-32.dp) - ) { val preferences = LocalPreferences.current Checkbox( @@ -217,8 +231,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { ) } PDEButton(onClick = { println("Open") }) { - val locale = LocalLocale.current - Text(locale["welcome.action.go"]) + Text( + text = locale["welcome.action.go"], + modifier = Modifier + ) } } } diff --git a/app/src/processing/app/ui/components/LanuageSelector.kt b/app/src/processing/app/ui/components/LanuageSelector.kt index 532e02d66f..801f0f008b 100644 --- a/app/src/processing/app/ui/components/LanuageSelector.kt +++ b/app/src/processing/app/ui/components/LanuageSelector.kt @@ -5,12 +5,16 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Text import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowForward +import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.outlined.Language import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp import processing.app.Platform import processing.app.ui.theme.LocalLocale @@ -88,12 +92,20 @@ fun LanguageChip(){ Image( imageVector = Icons.Outlined.Language, contentDescription = "Language", + colorFilter = ColorFilter.tint(color = LocalContentColor.current), modifier = Modifier .padding(start = 8.dp) .size(typography.body1.fontSize.value.dp) ) }) { Text(currentLanguage.name) + Image( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = locale["welcome.action.tutorials"], + colorFilter = ColorFilter.tint(color = LocalContentColor.current), + modifier = Modifier + .size(typography.body1.fontSize.value.dp) + ) DropdownMenu( expanded = expanded, onDismissRequest = { diff --git a/app/src/processing/app/ui/components/examples/Examples.kt b/app/src/processing/app/ui/components/examples/Examples.kt index 1c08dc6683..45455a2ca9 100644 --- a/app/src/processing/app/ui/components/examples/Examples.kt +++ b/app/src/processing/app/ui/components/examples/Examples.kt @@ -2,19 +2,21 @@ package processing.app.ui.components.examples import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme.colors +import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerIcon @@ -28,6 +30,7 @@ import org.jetbrains.compose.resources.decodeToImageBitmap import processing.app.LocalPreferences import processing.app.Messages import processing.app.Platform +import processing.app.ui.Welcome.Companion.LocalBase import java.awt.Cursor import java.io.File import java.nio.file.* @@ -121,46 +124,71 @@ fun rememberSketchbookPath(): File { } -@OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class) + @Composable fun examples(){ val examples = loadExamples() - // grab 4 random ones - val randoms = examples.shuffled().take(4) - LazyVerticalGrid( - columns = GridCells.Fixed(2), + + var randoms = examples.shuffled().take(4) + if(randoms.size < 4){ + randoms = randoms + List(4 - randoms.size) { Example( + folder = Paths.get(""), + library = Paths.get(""), + title = "Test", + image = ClassLoader.getSystemResource("default.png")?.toURI()?.let { Paths.get(it) } ?: Paths.get(""), + ) } + } + + Column( verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - ){ - items(randoms){ example -> - Column( - modifier = Modifier - .onPointerEvent(PointerEventType.Press) { - } - .onPointerEvent(PointerEventType.Release) { - } - .onPointerEvent(PointerEventType.Enter) { - } - .onPointerEvent(PointerEventType.Exit) { - } - .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) - ) { - val imageBitmap: ImageBitmap = remember(example.image) { - example.image.inputStream().readAllBytes().decodeToImageBitmap() + ) { + randoms.chunked(2).forEach { row -> + Row ( + horizontalArrangement = Arrangement.spacedBy(16.dp), + ){ + row.forEach { example -> + Example(example) } - Image( - painter = BitmapPainter(imageBitmap), - contentDescription = example.title, - modifier = Modifier - .background(colors.primary) - .width(185.dp) - .aspectRatio(16f / 9f) - ) - Text(example.title) } } - } } - +@OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class) +@Composable +fun Example(example: Example){ + val base = LocalBase.current + Button( + onClick = { + base?.handleOpenExample("${example.folder}/${example.title}.pde", base.defaultMode) + }, + contentPadding = PaddingValues(0.dp), + elevation = null, + shape = RectangleShape, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.Transparent, + contentColor = colors.onBackground + ), + ) { + Column( + modifier = Modifier + .width(185.dp) + ) { + val imageBitmap: ImageBitmap = remember(example.image) { + example.image.inputStream().readAllBytes().decodeToImageBitmap() + } + Image( + painter = BitmapPainter(imageBitmap), + contentDescription = example.title, + modifier = Modifier + .background(colors.primary) + .aspectRatio(16f / 9f) + ) + Text( + example.title, + style = typography.body1, + maxLines = 1 + ) + } + } +} diff --git a/app/src/processing/app/ui/theme/Button.kt b/app/src/processing/app/ui/theme/Button.kt index 5d2923ab0f..bec6dd3bcd 100644 --- a/app/src/processing/app/ui/theme/Button.kt +++ b/app/src/processing/app/ui/theme/Button.kt @@ -3,12 +3,15 @@ package processing.app.ui.theme import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* +import androidx.compose.material.Button import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.onPointerEvent @@ -18,41 +21,31 @@ import java.awt.Cursor @OptIn(ExperimentalComposeUiApi::class) @Composable -fun PDEButton(onClick: () -> Unit, content: @Composable BoxScope.() -> Unit) { - val theme = LocalTheme.current - +fun PDEButton(onClick: () -> Unit, content: @Composable RowScope.() -> Unit) { var hover by remember { mutableStateOf(false) } - var clicked by remember { mutableStateOf(false) } - val offset by animateFloatAsState(if (hover) -5f else 5f) - val color by animateColorAsState(if(clicked) colors.primaryVariant else colors.primary) + val offset by animateFloatAsState(if (hover) -3f else 3f) - Box(modifier = Modifier.padding(end = 5.dp, top = 5.dp)) { + Box { Box( modifier = Modifier .offset((-offset).dp, (offset).dp) - .background(theme.getColor("toolbar.button.pressed.field")) .matchParentSize() + .padding(vertical = 6.dp) + .background(colors.secondary) + ) - Box( + Button( + onClick = onClick, + shape = RectangleShape, + contentPadding = PaddingValues(vertical = 8.dp, horizontal = 32.dp), modifier = Modifier - .onPointerEvent(PointerEventType.Press) { - clicked = true - } - .onPointerEvent(PointerEventType.Release) { - clicked = false - onClick() - } .onPointerEvent(PointerEventType.Enter) { hover = true } .onPointerEvent(PointerEventType.Exit) { hover = false } - .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) - .background(color) - .padding(10.dp) - .sizeIn(minWidth = 100.dp), - contentAlignment = Alignment.Center, + .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))), content = content ) } diff --git a/app/src/processing/app/ui/theme/Chip.kt b/app/src/processing/app/ui/theme/Chip.kt index 6d44bc512a..baab6e8ef9 100644 --- a/app/src/processing/app/ui/theme/Chip.kt +++ b/app/src/processing/app/ui/theme/Chip.kt @@ -1,21 +1,13 @@ package processing.app.ui.theme import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Chip -import androidx.compose.material.ChipColors import androidx.compose.material.ChipDefaults import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme.colors import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @OptIn(ExperimentalMaterialApi::class) @@ -27,10 +19,10 @@ fun PDEChip( ){ Chip( onClick = onClick, - border = BorderStroke(1.dp, colors.onSurface.copy(alpha = 0.12f)), + border = BorderStroke(1.dp, colors.secondary), colors = ChipDefaults.chipColors( - backgroundColor = Color.Transparent, - contentColor = colors.onSurface + backgroundColor = colors.background, + contentColor = colors.primaryVariant ), leadingIcon = leadingIcon, modifier = Modifier, diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index 91e7d1e6b7..efa97d37cc 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -3,18 +3,31 @@ package processing.app.ui.theme import androidx.compose.material.Colors import androidx.compose.ui.graphics.Color -//val Colors = Colors( -// primary = Color("#0F195A".toColorInt()), -// primaryVariant = theme.getColor("toolbar.button.pressed.field"), -// secondary = theme.getColor("editor.gradient.bottom"), -// secondaryVariant = theme.getColor("editor.scrollbar.thumb.pressed.color"), -// background = theme.getColor("editor.bgcolor"), -// surface = theme.getColor("editor.bgcolor"), -// error = theme.getColor("status.error.bgcolor"), -// onPrimary = theme.getColor("toolbar.button.enabled.field"), -// onSecondary = theme.getColor("toolbar.button.enabled.field"), -// onBackground = theme.getColor("editor.fgcolor"), -// onSurface = theme.getColor("editor.fgcolor"), -// onError = theme.getColor("status.error.fgcolor"), -// isLight = theme.getProperty("laf.mode").equals("light") -//) \ No newline at end of file +val PDELightColors = Colors( + primary = Color("#0F195A".toColorInt()), + primaryVariant = Color("#1F34AB".toColorInt()), + secondary = Color("#82AFFF".toColorInt()), + secondaryVariant = Color("#0468FF".toColorInt()), + background = Color("#FFFFFF".toColorInt()), + surface = Color("#C0D7FF".toColorInt()), + error = Color("#0F195A".toColorInt()), + onPrimary = Color("#FFFFFF".toColorInt()), + onSecondary = Color("#FFFFFF".toColorInt()), + onBackground = Color("#0F195A".toColorInt()), + onSurface = Color("#FFFFFF".toColorInt()), + onError = Color("#0F195A".toColorInt()), + isLight = true, +) + +fun String.toColorInt(): Int { + if (this[0] == '#') { + var color = substring(1).toLong(16) + if (length == 7) { + color = color or 0x00000000ff000000L + } else if (length != 9) { + throw IllegalArgumentException("Unknown color") + } + return color.toInt() + } + throw IllegalArgumentException("Unknown color") +} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 6490c0eb90..1f0197d565 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -3,11 +3,7 @@ package processing.app.ui.theme import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection -import processing.app.LocalPreferences -import processing.app.Messages -import processing.app.Platform -import processing.app.PlatformStart -import processing.app.watchFile +import processing.app.* import java.io.File import java.io.InputStream import java.util.* @@ -53,6 +49,7 @@ fun LocaleProvider(content: @Composable () -> Unit) { java.util.Locale.setDefault(locale) languageFile.writeText(locale.language) code = locale.language + Language.reload() } diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 82428db7f6..aee7abe00f 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -1,7 +1,6 @@ package processing.app.ui.theme import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.Colors import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -33,43 +32,31 @@ fun ProcessingTheme( PreferencesProvider { val preferences = LocalPreferences.current val theme = Theme(preferences.getProperty("theme")) - val colors = Colors( - primary = theme.getColor("editor.gradient.top"), - primaryVariant = theme.getColor("toolbar.button.pressed.field"), - secondary = theme.getColor("editor.gradient.bottom"), - secondaryVariant = theme.getColor("editor.scrollbar.thumb.pressed.color"), - background = theme.getColor("editor.bgcolor"), - surface = theme.getColor("editor.bgcolor"), - error = theme.getColor("status.error.bgcolor"), - onPrimary = theme.getColor("toolbar.button.enabled.field"), - onSecondary = theme.getColor("toolbar.button.enabled.field"), - onBackground = theme.getColor("editor.fgcolor"), - onSurface = theme.getColor("editor.fgcolor"), - onError = theme.getColor("status.error.fgcolor"), - isLight = theme.getProperty("laf.mode").equals("light") - ) +// val colors = Colors( +// primary = theme.getColor("editor.gradient.top"), +// primaryVariant = theme.getColor("toolbar.button.pressed.field"), +// secondary = theme.getColor("editor.gradient.bottom"), +// secondaryVariant = theme.getColor("editor.scrollbar.thumb.pressed.color"), +// background = theme.getColor("editor.bgcolor"), +// surface = theme.getColor("editor.bgcolor"), +// error = theme.getColor("status.error.bgcolor"), +// onPrimary = theme.getColor("toolbar.button.enabled.field"), +// onSecondary = theme.getColor("toolbar.button.enabled.field"), +// onBackground = theme.getColor("editor.fgcolor"), +// onSurface = theme.getColor("editor.fgcolor"), +// onError = theme.getColor("status.error.fgcolor"), +// isLight = theme.getProperty("laf.mode").equals("light") +// ) + CompositionLocalProvider(LocalTheme provides theme) { LocaleProvider { MaterialTheme( - colors = colors, + colors = if(darkTheme) PDELightColors else PDELightColors, typography = Typography, content = content ) } } } -} - -fun String.toColorInt(): Int { - if (this[0] == '#') { - var color = substring(1).toLong(16) - if (length == 7) { - color = color or 0x00000000ff000000L - } else if (length != 9) { - throw IllegalArgumentException("Unknown color") - } - return color.toInt() - } - throw IllegalArgumentException("Unknown color") } \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index 26b45d9563..8ad5532d1d 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -1,10 +1,10 @@ package processing.app.ui.theme import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -91,4 +91,33 @@ fun pdeapplication(titleKey: String = "", fullWindowContent: Boolean = false,con } } } -} \ No newline at end of file +} + + +fun main(){ + application { + val windowState = rememberWindowState( + size = DpSize.Unspecified, + position = WindowPosition(Alignment.Center) + ) + Window(onCloseRequest = ::exitApplication, title = "Welcome to Processing", state = windowState) { + Row{ + Text("Hello, Processing!") + Column( + modifier = Modifier +// .wrapContentSize() + ) { + Box( + modifier = Modifier + .background(Color.Red) + .sizeIn(minHeight = 100.dp) + ) { + + } + Text("Hello, Processing!") + } + } + + } + } +} From 3df4da94618ed66d9a2b48a601b0ce7bac9a2d4e Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Feb 2025 12:57:55 +0100 Subject: [PATCH 09/50] Welcome Screen: Bugfixes --- app/src/processing/app/Language.java | 1 + app/src/processing/app/ui/Welcome.kt | 22 ++++++++++- .../app/ui/components/LanuageSelector.kt | 11 +++--- .../app/ui/components/examples/Examples.kt | 4 +- app/src/processing/app/ui/theme/Window.kt | 38 +++++-------------- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java index f0fdab8759..bcc4385a53 100644 --- a/app/src/processing/app/Language.java +++ b/app/src/processing/app/Language.java @@ -184,6 +184,7 @@ static public Language init() { } static public void reload(){ + if(instance == null) return; synchronized (Language.class) { instance = new Language(); } diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 754c9e4fc6..98c8c2ed49 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -14,11 +14,14 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import com.formdev.flatlaf.util.SystemInfo @@ -90,6 +93,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { .height(200.dp) .offset (32.dp) .align(Alignment.BottomEnd) + .scale(when(LocalLayoutDirection.current) { + LayoutDirection.Rtl -> -1f + else -> 1f + }, 1f) ) } } @@ -139,7 +146,12 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { painter = painterResource("welcome/intro/bubble.svg"), contentDescription = locale["welcome.intro.long"], modifier = Modifier - .align{ _, space, _ -> space / 4 } + .align(Alignment.Start) + .scale(when(LocalLayoutDirection.current) { + LayoutDirection.Rtl -> -1f + else -> 1f + }, 1f) + .padding(start = 64.dp) ) Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -151,6 +163,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { contentDescription = locale["welcome.intro.long"], modifier = Modifier .offset(x = -32.dp) + .scale(when(LocalLayoutDirection.current) { + LayoutDirection.Rtl -> -1f + else -> 1f + }, 1f) ) Image( painter = painterResource("welcome/intro/short.svg"), @@ -158,6 +174,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { modifier = Modifier .align(Alignment.Bottom) .offset(x = 16.dp, y = -16.dp) + .scale(when(LocalLayoutDirection.current) { + LayoutDirection.Rtl -> -1f + else -> 1f + }, 1f) ) } } diff --git a/app/src/processing/app/ui/components/LanuageSelector.kt b/app/src/processing/app/ui/components/LanuageSelector.kt index 801f0f008b..5c42443fe4 100644 --- a/app/src/processing/app/ui/components/LanuageSelector.kt +++ b/app/src/processing/app/ui/components/LanuageSelector.kt @@ -21,6 +21,7 @@ import processing.app.ui.theme.LocalLocale import processing.app.ui.theme.PDEChip import processing.app.watchFile import java.io.File +import java.nio.file.FileSystem import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Paths @@ -34,6 +35,8 @@ data class Language( val properties: Properties ) +var jarFs: FileSystem? = null + @Composable fun LanguageChip(){ var expanded by remember { mutableStateOf(false) } @@ -44,9 +47,6 @@ fun LanguageChip(){ val main = ClassLoader.getSystemResource("PDE.properties")?: return - - - val languages = remember { val list = when(main.protocol){ "file" -> { @@ -55,8 +55,8 @@ fun LanguageChip(){ } "jar" -> { val uri = main.toURI() - val fs = FileSystems.newFileSystem(uri, emptyMap()) - Files.list(fs.getPath("/")) + jarFs = jarFs ?: FileSystems.newFileSystem(uri, emptyMap()) ?: return@remember null + Files.list(jarFs!!.getPath("/")) } else -> null } ?: return@remember null @@ -81,6 +81,7 @@ fun LanguageChip(){ ) } } + .sortedBy { it.name.lowercase() } } ?: return val current = languageFile.readText(Charsets.UTF_8).substring(0, 2) diff --git a/app/src/processing/app/ui/components/examples/Examples.kt b/app/src/processing/app/ui/components/examples/Examples.kt index 45455a2ca9..4c0a9045cb 100644 --- a/app/src/processing/app/ui/components/examples/Examples.kt +++ b/app/src/processing/app/ui/components/examples/Examples.kt @@ -135,7 +135,7 @@ fun examples(){ randoms = randoms + List(4 - randoms.size) { Example( folder = Paths.get(""), library = Paths.get(""), - title = "Test", + title = "Example", image = ClassLoader.getSystemResource("default.png")?.toURI()?.let { Paths.get(it) } ?: Paths.get(""), ) } } @@ -154,7 +154,7 @@ fun examples(){ } } } -@OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class) +@OptIn(ExperimentalResourceApi::class) @Composable fun Example(example: Example){ val base = LocalBase.current diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index 8ad5532d1d..23b71ebada 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -6,6 +6,7 @@ import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel @@ -41,6 +42,10 @@ class PDEWindow(titleKey: String = "", fullWindowContent: Boolean = false, conte ProcessingTheme { val locale = LocalLocale.current this@PDEWindow.title = locale[titleKey] + LaunchedEffect(locale){ + this@PDEWindow.pack() + this@PDEWindow.setLocationRelativeTo(null) + } Box(modifier = Modifier .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) @@ -81,6 +86,10 @@ fun pdeapplication(titleKey: String = "", fullWindowContent: Boolean = false,con putClientProperty("apple.awt.fullWindowContent", mac) putClientProperty("apple.awt.transparentTitleBar", mac) } + LaunchedEffect(locale){ + window.pack() + window.setLocationRelativeTo(null) + } Surface(color = colors.background) { Box(modifier = Modifier .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) @@ -92,32 +101,3 @@ fun pdeapplication(titleKey: String = "", fullWindowContent: Boolean = false,con } } } - - -fun main(){ - application { - val windowState = rememberWindowState( - size = DpSize.Unspecified, - position = WindowPosition(Alignment.Center) - ) - Window(onCloseRequest = ::exitApplication, title = "Welcome to Processing", state = windowState) { - Row{ - Text("Hello, Processing!") - Column( - modifier = Modifier -// .wrapContentSize() - ) { - Box( - modifier = Modifier - .background(Color.Red) - .sizeIn(minHeight = 100.dp) - ) { - - } - Text("Hello, Processing!") - } - } - - } - } -} From 8d8bd01d806257367b1a006f0da23b926b8f8ae3 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Feb 2025 13:54:07 +0100 Subject: [PATCH 10/50] Welcome Screen: Close Functionality --- app/src/processing/app/ui/Welcome.kt | 5 ++- app/src/processing/app/ui/WelcomeToBeta.kt | 5 ++- app/src/processing/app/ui/theme/Window.kt | 47 ++++++++++++---------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 98c8c2ed49..492410b881 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -250,7 +250,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { text = locale["welcome.action.startup"], ) } - PDEButton(onClick = { println("Open") }) { + val window = LocalWindow.current + PDEButton(onClick = { + window.dispose() + }) { Text( text = locale["welcome.action.go"], modifier = Modifier diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index ac8302fb81..8f50ef6f8e 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -54,7 +54,7 @@ class WelcomeToBeta { } @Composable - fun welcomeToBeta(close: () -> Unit = {}) { + fun welcomeToBeta() { Row( modifier = Modifier .padding(20.dp, 10.dp) @@ -87,9 +87,10 @@ class WelcomeToBeta { text = locale["beta.message"] ) Row { + val window = LocalWindow.current Spacer(modifier = Modifier.weight(1f)) PDEButton(onClick = { - close() + window.dispose() }) { Text( text = locale["beta.button"], diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index 23b71ebada..0cb419332c 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -1,18 +1,15 @@ package processing.app.ui.theme -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.Surface -import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window @@ -20,12 +17,12 @@ import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import com.formdev.flatlaf.util.SystemInfo -import processing.app.ui.WelcomeToBeta.Companion.welcomeToBeta import java.awt.event.KeyAdapter import java.awt.event.KeyEvent import javax.swing.JFrame +val LocalWindow = compositionLocalOf { error("No Window Set") } class PDEWindow(titleKey: String = "", fullWindowContent: Boolean = false, content: @Composable () -> Unit): JFrame(){ init{ @@ -39,19 +36,22 @@ class PDEWindow(titleKey: String = "", fullWindowContent: Boolean = false, conte defaultCloseOperation = DISPOSE_ON_CLOSE ComposePanel().apply { setContent { - ProcessingTheme { - val locale = LocalLocale.current - this@PDEWindow.title = locale[titleKey] - LaunchedEffect(locale){ - this@PDEWindow.pack() - this@PDEWindow.setLocationRelativeTo(null) - } + CompositionLocalProvider(LocalWindow provides this@PDEWindow) { + ProcessingTheme { + val locale = LocalLocale.current + this@PDEWindow.title = locale[titleKey] + LaunchedEffect(locale) { + this@PDEWindow.pack() + this@PDEWindow.setLocationRelativeTo(null) + } - Box(modifier = Modifier - .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) - ) { - content() + Box( + modifier = Modifier + .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) + ) { + content() + } } } } @@ -90,11 +90,14 @@ fun pdeapplication(titleKey: String = "", fullWindowContent: Boolean = false,con window.pack() window.setLocationRelativeTo(null) } - Surface(color = colors.background) { - Box(modifier = Modifier - .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) - ) { - content() + CompositionLocalProvider(LocalWindow provides window) { + Surface(color = colors.background) { + Box( + modifier = Modifier + .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) + ) { + content() + } } } } From 54944afc24a1058bf6fcc5a9899bfdeaa1283229 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Feb 2025 14:36:37 +0100 Subject: [PATCH 11/50] Welcome Screen: JDK Module --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bfcb65cc07..297892512c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,7 +50,7 @@ compose.desktop { mainClass = "processing.app.ui.Start" nativeDistributions{ - modules("jdk.jdi", "java.compiler") + modules("jdk.jdi", "java.compiler", "jdk.zipfs") targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "Processing" packageVersion = rootProject.version.toString() From b0e7f96bddafc1a40c59d47cd81ef9919632ce84 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 17 Mar 2025 10:28:12 +0100 Subject: [PATCH 12/50] Added initial tests --- app/build.gradle.kts | 4 +++ app/src/processing/app/Preferences.kt | 6 +++- app/test/processing/app/PreferencesKtTest.kt | 34 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 app/test/processing/app/PreferencesKtTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb060234b2..fe2ab72499 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,6 @@ import org.gradle.kotlin.dsl.support.zipTo import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download @@ -121,6 +122,9 @@ dependencies { testImplementation(libs.mockitoKotlin) testImplementation(libs.junitJupiter) testImplementation(libs.junitJupiterParams) + + @OptIn(ExperimentalComposeLibrary::class) + testImplementation(compose.uiTest) } tasks.test { diff --git a/app/src/processing/app/Preferences.kt b/app/src/processing/app/Preferences.kt index 5c0751d87a..8a9dc0f7d6 100644 --- a/app/src/processing/app/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -32,6 +32,10 @@ class ReactiveProperties: Properties() { } operator fun get(key: String): String? = getProperty(key) + + operator fun set(key: String, value: String) { + setProperty(key, value) + } } val LocalPreferences = compositionLocalOf { error("No preferences provided") } @OptIn(FlowPreview::class) @@ -53,7 +57,7 @@ fun PreferencesProvider(content: @Composable () -> Unit){ LaunchedEffect(properties) { snapshotFlow { properties._stateMap.toMap() } .dropWhile { it == initialState } - .debounce(1000) + .debounce(100) .collect { preferencesFile.outputStream().use { output -> output.write( diff --git a/app/test/processing/app/PreferencesKtTest.kt b/app/test/processing/app/PreferencesKtTest.kt new file mode 100644 index 0000000000..f38796668e --- /dev/null +++ b/app/test/processing/app/PreferencesKtTest.kt @@ -0,0 +1,34 @@ +package processing.app + +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.* +import kotlin.test.Test + +class PreferencesKtTest{ + @OptIn(ExperimentalTestApi::class) + @Test + fun testKeyReactivity() = runComposeUiTest { + val newValue = (0..Int.MAX_VALUE).random().toString() + val testKey = "test.preferences.reactivity.$newValue" + setContent { + PreferencesProvider { + val preferences = LocalPreferences.current + Text(preferences[testKey] ?: "default", modifier = Modifier.testTag("text")) + + Button(onClick = { + preferences[testKey] = newValue + }, modifier = Modifier.testTag("button")) { + Text("Change") + } + } + } + + onNodeWithTag("text").assertTextEquals("default") + onNodeWithTag("button").performClick() + onNodeWithTag("text").assertTextEquals(newValue) + } + +} \ No newline at end of file From d17ca0cdcda2080cf458c0352fc89e80ef4f57a7 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 17 Mar 2025 10:36:35 +0100 Subject: [PATCH 13/50] Removed PlatformStart and create new file if doesn't exist --- app/src/processing/app/Preferences.kt | 10 ++++++---- app/src/processing/app/ui/theme/Locale.kt | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/processing/app/Preferences.kt b/app/src/processing/app/Preferences.kt index 8a9dc0f7d6..7bfadcd733 100644 --- a/app/src/processing/app/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -16,9 +16,6 @@ import java.util.Properties const val PREFERENCES_FILE_NAME = "preferences.txt" const val DEFAULTS_FILE_NAME = "defaults.txt" -fun PlatformStart(){ - Platform.inst ?: Platform.init() -} class ReactiveProperties: Properties() { val _stateMap = mutableStateMapOf() @@ -41,10 +38,15 @@ val LocalPreferences = compositionLocalOf { error("No prefer @OptIn(FlowPreview::class) @Composable fun PreferencesProvider(content: @Composable () -> Unit){ - PlatformStart() + remember { + Platform.init() + } val settingsFolder = Platform.getSettingsFolder() val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME) + if(!preferencesFile.exists()){ + preferencesFile.createNewFile() + } val update = watchFile(preferencesFile) val properties = remember(preferencesFile, update) { ReactiveProperties().apply { diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 1f0197d565..a4fd9eecfc 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -38,7 +38,9 @@ class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : val LocalLocale = compositionLocalOf { error("No Locale Set") } @Composable fun LocaleProvider(content: @Composable () -> Unit) { - PlatformStart() + remember { + Platform.init() + } val settingsFolder = Platform.getSettingsFolder() val languageFile = File(settingsFolder, "language.txt") From f7f22c5f7ce8434a058d3cc5411f541777cc637e Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 17 Mar 2025 10:40:37 +0100 Subject: [PATCH 14/50] Create Directory as well --- app/src/processing/app/Preferences.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/processing/app/Preferences.kt b/app/src/processing/app/Preferences.kt index 7bfadcd733..a31cef2bbe 100644 --- a/app/src/processing/app/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -45,6 +45,7 @@ fun PreferencesProvider(content: @Composable () -> Unit){ val settingsFolder = Platform.getSettingsFolder() val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME) if(!preferencesFile.exists()){ + preferencesFile.mkdirs() preferencesFile.createNewFile() } From 1a3bc46391ac8a2bbc27597fa7a8e05ee5c2f755 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 10 Sep 2025 07:57:15 +0200 Subject: [PATCH 15/50] Update bug1532.pde --- java/test/resources/bug1532.pde | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/test/resources/bug1532.pde b/java/test/resources/bug1532.pde index 66b24b7779..2fb0f0f363 100644 --- a/java/test/resources/bug1532.pde +++ b/java/test/resources/bug1532.pde @@ -20,7 +20,7 @@ flatCube[][] grid; void setup() { try { - quicktime.QTSession.open(); + // quicktime.QTSession.open(); } catch (quicktime.QTException qte) { qte.printStackTrace(); From 413546064610dc4f5c7a4e8b29e213c1c8ba7905 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 11 Sep 2025 17:36:21 +0200 Subject: [PATCH 16/50] Add test message triggers to Develop menu Introduces a new menu item in the Develop menu to trigger various message dialogs for testing purposes. Also adds TODO comments in Theme.kt for future UI improvements and references. --- app/src/processing/app/ui/Editor.java | 8 ++++++++ app/src/processing/app/ui/theme/Theme.kt | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index df2440d391..5b752b4046 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1061,6 +1061,14 @@ public void buildDevelopMenu(){ }); developMenu.add(updateTrigger); + var error = new JMenuItem(Language.text("menu.develop.trigger.messages")); + error.addActionListener(e -> { + Messages.showWarning("Hello", "This is a test of the emergency broadcast system.\n\nIf this had been an actual emergency, you would be instructed to tune to your local news station.", new Exception("This is a test exception for developers.")); + Messages.showYesNoQuestion(this, "Test Question", "Did you see the exception in the console?", null); + Messages.showError("Test Error", "This is a test error message. the editor will quit after this", new Exception("This is a test exception for developers.")); + }); + developMenu.add(error); + } public void updateDevelopMenu(){ diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index aee7abe00f..db12aab0f9 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -12,6 +12,13 @@ import java.io.InputStream import java.util.Properties +// TODO: Other monospace font +// TODO: Look at flatlaf components +// TODO: Look at Jetpack Compose default components +// TODO: Message Boxes +// TODO: Tabs +// TODO: https://web.mit.edu/6.005/www/sp14/psets/ps4/java-6-tutorial/components.html + class Theme(themeFile: String? = "") : Properties() { init { load(ClassLoader.getSystemResourceAsStream("theme.txt")) From 38c8001afa03dffb73a084a37c64fbdf3fe8455b Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 14 Oct 2025 11:49:44 +0200 Subject: [PATCH 17/50] Enhance Preferences reactivity and test coverage Refactored ReactiveProperties to use snapshotStateMap for Compose reactivity. Improved PreferencesProvider and watchFile composables with better file watching, override support via system properties, and added documentation. Updated PreferencesKtTest to use temporary files and verify file-to-UI reactivity. --- app/src/processing/app/Preferences.kt | 121 ++++++++++++++++--- app/test/processing/app/PreferencesKtTest.kt | 31 ++++- 2 files changed, 131 insertions(+), 21 deletions(-) diff --git a/app/src/processing/app/Preferences.kt b/app/src/processing/app/Preferences.kt index a31cef2bbe..c54cbbd817 100644 --- a/app/src/processing/app/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -8,24 +8,24 @@ import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.launch import java.io.File import java.io.InputStream -import java.io.OutputStream import java.nio.file.* import java.util.Properties - -const val PREFERENCES_FILE_NAME = "preferences.txt" -const val DEFAULTS_FILE_NAME = "defaults.txt" - +/* + The ReactiveProperties class extends the standard Java Properties class + to provide reactive capabilities using Jetpack Compose's mutableStateMapOf. + This allows UI components to automatically update when preference values change. +*/ class ReactiveProperties: Properties() { - val _stateMap = mutableStateMapOf() + val snapshotStateMap = mutableStateMapOf() override fun setProperty(key: String, value: String) { super.setProperty(key, value) - _stateMap[key] = value + snapshotStateMap[key] = value } override fun getProperty(key: String): String? { - return _stateMap[key] ?: super.getProperty(key) + return snapshotStateMap[key] ?: super.getProperty(key) } operator fun get(key: String): String? = getProperty(key) @@ -34,34 +34,88 @@ class ReactiveProperties: Properties() { setProperty(key, value) } } + +/* + A CompositionLocal to provide access to the ReactiveProperties instance + throughout the composable hierarchy. + */ val LocalPreferences = compositionLocalOf { error("No preferences provided") } + +const val PREFERENCES_FILE_NAME = "preferences.txt" +const val DEFAULTS_FILE_NAME = "defaults.txt" + +/* + This composable function sets up a preferences provider that manages application settings. + It initializes the preferences from a file, watches for changes to that file, and saves + any updates back to the file. It uses a ReactiveProperties class to allow for reactive + updates in the UI when preferences change. + + usage: + PreferencesProvider { + // Your app content here + } + + to access preferences: + val preferences = LocalPreferences.current + val someSetting = preferences["someKey"] ?: "defaultValue" + preferences["someKey"] = "newValue" + + This will automatically save to the preferences file and update any UI components + that are observing that key. + + to override the preferences file (for testing, etc) + System.setProperty("processing.app.preferences.file", "/path/to/your/preferences.txt") + to override the debounce time (in milliseconds) + System.setProperty("processing.app.preferences.debounce", "200") + + */ @OptIn(FlowPreview::class) @Composable fun PreferencesProvider(content: @Composable () -> Unit){ + val preferencesFileOverride: File? = System.getProperty("processing.app.preferences.file")?.let { File(it) } + val preferencesDebounceOverride: Long? = System.getProperty("processing.app.preferences.debounce")?.toLongOrNull() + + // Initialize the platform (if not already done) to ensure we have access to the settings folder remember { Platform.init() } + // Grab the preferences file, creating it if it doesn't exist + // TODO: This functionality should be separated from the `Preferences` class itself val settingsFolder = Platform.getSettingsFolder() - val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME) + val preferencesFile = preferencesFileOverride ?: settingsFolder.resolve(PREFERENCES_FILE_NAME) if(!preferencesFile.exists()){ preferencesFile.mkdirs() preferencesFile.createNewFile() } val update = watchFile(preferencesFile) - val properties = remember(preferencesFile, update) { ReactiveProperties().apply { - load((ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME)?: InputStream.nullInputStream()).reader(Charsets.UTF_8)) - load(preferencesFile.inputStream().reader(Charsets.UTF_8)) - }} - val initialState = remember(properties) { properties._stateMap.toMap() } + val properties = remember(preferencesFile, update) { + ReactiveProperties().apply { + val defaultsStream = ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME) + ?: InputStream.nullInputStream() + load(defaultsStream + .reader(Charsets.UTF_8) + ) + load(preferencesFile + .inputStream() + .reader(Charsets.UTF_8) + ) + } + } + + val initialState = remember(properties) { properties.snapshotStateMap.toMap() } + + // Listen for changes to the preferences and save them to file LaunchedEffect(properties) { - snapshotFlow { properties._stateMap.toMap() } + snapshotFlow { properties.snapshotStateMap.toMap() } .dropWhile { it == initialState } - .debounce(100) + .debounce(preferencesDebounceOverride ?: 100) .collect { + + // Save the preferences to file, sorted alphabetically preferencesFile.outputStream().use { output -> output.write( properties.entries @@ -78,24 +132,53 @@ fun PreferencesProvider(content: @Composable () -> Unit){ } } + +/* + This composable function watches a specified file for modifications. When the file is modified, + it updates a state variable with the latest WatchEvent. This can be useful for triggering UI updates + or other actions in response to changes in the file. + + To watch the file at the fasted speed (for testing) set the following system property: + System.setProperty("processing.app.watchfile.forced", "true") + */ @Composable fun watchFile(file: File): Any? { + val forcedWatch: Boolean = System.getProperty("processing.app.watchfile.forced").toBoolean() + val scope = rememberCoroutineScope() var event by remember(file) { mutableStateOf?> (null) } DisposableEffect(file){ val fileSystem = FileSystems.getDefault() val watcher = fileSystem.newWatchService() + var active = true + // In forced mode we just poll the last modified time of the file + // This is not efficient but works better for testing with temp files + val toWatch = { file.lastModified() } + var state = toWatch() + val path = file.toPath() val parent = path.parent val key = parent.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY) scope.launch(Dispatchers.IO) { while (active) { - for (modified in key.pollEvents()) { - if (modified.context() != path.fileName) continue - event = modified + if(forcedWatch) { + if(toWatch() == state) continue + state = toWatch() + event = object : WatchEvent { + override fun count(): Int = 1 + override fun context(): Path = file.toPath().fileName + override fun kind(): WatchEvent.Kind = StandardWatchEventKinds.ENTRY_MODIFY + override fun toString(): String = "ForcedEvent(${context()})" + } + continue + }else{ + for (modified in key.pollEvents()) { + if (modified.context() != path.fileName) continue + event = modified + } } } } diff --git a/app/test/processing/app/PreferencesKtTest.kt b/app/test/processing/app/PreferencesKtTest.kt index f38796668e..6b5dbc5ea9 100644 --- a/app/test/processing/app/PreferencesKtTest.kt +++ b/app/test/processing/app/PreferencesKtTest.kt @@ -5,14 +5,29 @@ import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.* +import java.util.Properties +import kotlin.io.path.createFile +import kotlin.io.path.createTempDirectory import kotlin.test.Test class PreferencesKtTest{ @OptIn(ExperimentalTestApi::class) @Test fun testKeyReactivity() = runComposeUiTest { + val directory = createTempDirectory("preferences") + val tempPreferences = directory + .resolve("preferences.txt") + .createFile() + .toFile() + + // Set system properties for testing + System.setProperty("processing.app.preferences.file", tempPreferences.absolutePath) + System.setProperty("processing.app.preferences.debounce", "0") + System.setProperty("processing.app.watchfile.forced", "true") + val newValue = (0..Int.MAX_VALUE).random().toString() - val testKey = "test.preferences.reactivity.$newValue" + val testKey = "test.preferences.reactivity" + setContent { PreferencesProvider { val preferences = LocalPreferences.current @@ -29,6 +44,18 @@ class PreferencesKtTest{ onNodeWithTag("text").assertTextEquals("default") onNodeWithTag("button").performClick() onNodeWithTag("text").assertTextEquals(newValue) - } + val preferences = Properties() + preferences.load(tempPreferences.inputStream().reader(Charsets.UTF_8)) + + // Check if the preference was saved to file + assert(preferences[testKey] == newValue) + + + val nextValue = (0..Int.MAX_VALUE).random().toString() + // Overwrite the file to see if the UI updates + tempPreferences.writeText("$testKey=${nextValue}") + + onNodeWithTag("text").assertTextEquals(nextValue) + } } \ No newline at end of file From 750ff8d1cddb8ba77b51f5cae5bb357c3ff4b5db Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 11:35:48 +0200 Subject: [PATCH 18/50] Refactor Locale class and add LocaleProvider test Improved documentation and refactored the Locale class for better clarity and resource loading. Enhanced LocaleProvider to support preferences folder override and language file management. Added a comprehensive Compose UI test for LocaleProvider to verify localization and language preference persistence. --- app/src/processing/app/ui/theme/Locale.kt | 88 +++++++++++++++++++---- app/test/processing/app/LocaleKtTest.kt | 52 ++++++++++++++ 2 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 app/test/processing/app/LocaleKtTest.kt diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index a4fd9eecfc..0879418a88 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -8,22 +8,36 @@ import java.io.File import java.io.InputStream import java.util.* +/** + * The Locale class extends the standard Java Properties class + * to provide localization capabilities. + * It loads localization resources from property files based on the specified language code. + * The class also provides a method to change the current locale and update the application accordingly. + * Usage: + * ``` + * val locale = Locale("es") { newLocale -> + * // Handle locale change, e.g., update UI or restart application + * } + * val localizedString = locale["someKey"] + * ``` + */ class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : Properties() { var locale: java.util.Locale = java.util.Locale.getDefault() init { - fun loadResourceUTF8(path: String) { - val stream = ClassLoader.getSystemResourceAsStream(path) - stream?.reader(charset = Charsets.UTF_8)?.use { reader -> - load(reader) - } - } loadResourceUTF8("PDE.properties") loadResourceUTF8("PDE_${locale.language}.properties") loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties") loadResourceUTF8("PDE_${language}.properties") } + fun loadResourceUTF8(path: String) { + val stream = ClassLoader.getSystemResourceAsStream(path) + stream?.reader(charset = Charsets.UTF_8)?.use { reader -> + load(reader) + } + } + @Deprecated("Use get instead", ReplaceWith("get(key)")) override fun getProperty(key: String?, default: String): String { val value = super.getProperty(key, default) @@ -35,28 +49,74 @@ class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : setLocale(locale) } } +/** + * A CompositionLocal to provide access to the Locale instance + * throughout the composable hierarchy. see [LocaleProvider] + * Usage: + * ``` + * val locale = LocalLocale.current + * val localizedString = locale["someKey"] + * ``` + */ val LocalLocale = compositionLocalOf { error("No Locale Set") } + +/** + * This composable function sets up a locale provider that manages application localization. + * It initializes the locale from a language file, watches for changes to that file, and updates + * the locale accordingly. It uses a [Locale] class to handle loading of localized resources. + * + * Usage: + * ``` + * LocaleProvider { + * // Your app content here + * } + * ``` + * + * To access the locale: + * ``` + * val locale = LocalLocale.current + * val localizedString = locale["someKey"] + * ``` + * + * To change the locale: + * ``` + * locale.set(java.util.Locale("es")) + * ``` + * This will update the `language.txt` file and reload the locale. + */ @Composable fun LocaleProvider(content: @Composable () -> Unit) { - remember { + val preferencesFolderOverride: File? = System.getProperty("processing.app.preferences.folder")?.let { File(it) } + + val settingsFolder = preferencesFolderOverride ?: remember{ Platform.init() + Platform.getSettingsFolder() } + val languageFile = settingsFolder.resolve("language.txt") + remember(languageFile){ + if(languageFile.exists()) return@remember - val settingsFolder = Platform.getSettingsFolder() - val languageFile = File(settingsFolder, "language.txt") - watchFile(languageFile) - var code by remember{ mutableStateOf(languageFile.readText().substring(0, 2)) } + Messages.log("Creating language file at ${languageFile.absolutePath}") + settingsFolder.mkdirs() + languageFile.writeText(java.util.Locale.getDefault().language) + } - fun setLocale(locale: java.util.Locale) { + val update = watchFile(languageFile) + var code by remember(languageFile, update){ mutableStateOf(languageFile.readText().substring(0, 2)) } + remember(code) { + val locale = Locale(code) java.util.Locale.setDefault(locale) + } + + fun setLocale(locale: java.util.Locale) { + Messages.log("Setting locale to ${locale.language}") languageFile.writeText(locale.language) code = locale.language - Language.reload() } val locale = Locale(code, ::setLocale) - Messages.log("Locale: $code") + remember(code) { Messages.log("Loaded Locale: $code") } val dir = when(locale["locale.direction"]) { "rtl" -> LayoutDirection.Rtl else -> LayoutDirection.Ltr diff --git a/app/test/processing/app/LocaleKtTest.kt b/app/test/processing/app/LocaleKtTest.kt new file mode 100644 index 0000000000..a4e7d637cf --- /dev/null +++ b/app/test/processing/app/LocaleKtTest.kt @@ -0,0 +1,52 @@ +package processing.app + +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.runComposeUiTest +import processing.app.ui.theme.LocalLocale +import processing.app.ui.theme.LocaleProvider +import kotlin.io.path.createTempDirectory +import kotlin.test.Test + +class LocaleKtTest { + @OptIn(ExperimentalTestApi::class) + @Test + fun testLocale() = runComposeUiTest { + val tempPreferencesDir = createTempDirectory("preferences") + + System.setProperty("processing.app.preferences.folder", tempPreferencesDir.toFile().absolutePath) + + setContent { + LocaleProvider { + val locale = LocalLocale.current + Text(locale["menu.file.new"], modifier = Modifier.testTag("localisedText")) + + Button(onClick = { + locale.setLocale(java.util.Locale("es")) + }, modifier = Modifier.testTag("button")) { + Text("Change") + } + } + } + + // Check if usage generates the language file if it doesn't exist + val languageFile = tempPreferencesDir.resolve("language.txt").toFile() + assert(languageFile.exists()) + + // Check if the text is localised + onNodeWithTag("localisedText").assertTextEquals("New") + + // Change the locale to Spanish + onNodeWithTag("button").performClick() + onNodeWithTag("localisedText").assertTextEquals("Nuevo") + + // Check if the preference was saved to file + assert(languageFile.readText().substring(0, 2) == "es") + } +} \ No newline at end of file From 4546a3878ace2fca7027b27a14817fc0039b35d6 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 11:39:03 +0200 Subject: [PATCH 19/50] Refactor Locale class and add LocaleProvider test --- app/src/processing/app/ui/theme/Locale.kt | 129 ++++++++++++++++++---- app/test/processing/app/LocaleKtTest.kt | 52 +++++++++ 2 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 app/test/processing/app/LocaleKtTest.kt diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 254c0946c1..0879418a88 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -1,24 +1,41 @@ package processing.app.ui.theme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import processing.app.LocalPreferences -import processing.app.Messages -import processing.app.Platform -import processing.app.PlatformStart -import processing.app.watchFile +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection +import processing.app.* import java.io.File import java.io.InputStream import java.util.* -class Locale(language: String = "") : Properties() { +/** + * The Locale class extends the standard Java Properties class + * to provide localization capabilities. + * It loads localization resources from property files based on the specified language code. + * The class also provides a method to change the current locale and update the application accordingly. + * Usage: + * ``` + * val locale = Locale("es") { newLocale -> + * // Handle locale change, e.g., update UI or restart application + * } + * val localizedString = locale["someKey"] + * ``` + */ +class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : Properties() { + var locale: java.util.Locale = java.util.Locale.getDefault() + init { - val locale = java.util.Locale.getDefault() - load(ClassLoader.getSystemResourceAsStream("PDE.properties")) - load(ClassLoader.getSystemResourceAsStream("PDE_${locale.language}.properties") ?: InputStream.nullInputStream()) - load(ClassLoader.getSystemResourceAsStream("PDE_${locale.toLanguageTag()}.properties") ?: InputStream.nullInputStream()) - load(ClassLoader.getSystemResourceAsStream("PDE_${language}.properties") ?: InputStream.nullInputStream()) + loadResourceUTF8("PDE.properties") + loadResourceUTF8("PDE_${locale.language}.properties") + loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties") + loadResourceUTF8("PDE_${language}.properties") + } + + fun loadResourceUTF8(path: String) { + val stream = ClassLoader.getSystemResourceAsStream(path) + stream?.reader(charset = Charsets.UTF_8)?.use { reader -> + load(reader) + } } @Deprecated("Use get instead", ReplaceWith("get(key)")) @@ -28,18 +45,86 @@ class Locale(language: String = "") : Properties() { return value } operator fun get(key: String): String = getProperty(key, key) + fun set(locale: java.util.Locale) { + setLocale(locale) + } } -val LocalLocale = compositionLocalOf { Locale() } +/** + * A CompositionLocal to provide access to the Locale instance + * throughout the composable hierarchy. see [LocaleProvider] + * Usage: + * ``` + * val locale = LocalLocale.current + * val localizedString = locale["someKey"] + * ``` + */ +val LocalLocale = compositionLocalOf { error("No Locale Set") } + +/** + * This composable function sets up a locale provider that manages application localization. + * It initializes the locale from a language file, watches for changes to that file, and updates + * the locale accordingly. It uses a [Locale] class to handle loading of localized resources. + * + * Usage: + * ``` + * LocaleProvider { + * // Your app content here + * } + * ``` + * + * To access the locale: + * ``` + * val locale = LocalLocale.current + * val localizedString = locale["someKey"] + * ``` + * + * To change the locale: + * ``` + * locale.set(java.util.Locale("es")) + * ``` + * This will update the `language.txt` file and reload the locale. + */ @Composable fun LocaleProvider(content: @Composable () -> Unit) { - PlatformStart() + val preferencesFolderOverride: File? = System.getProperty("processing.app.preferences.folder")?.let { File(it) } + + val settingsFolder = preferencesFolderOverride ?: remember{ + Platform.init() + Platform.getSettingsFolder() + } + val languageFile = settingsFolder.resolve("language.txt") + remember(languageFile){ + if(languageFile.exists()) return@remember - val settingsFolder = Platform.getSettingsFolder() - val languageFile = File(settingsFolder, "language.txt") - watchFile(languageFile) + Messages.log("Creating language file at ${languageFile.absolutePath}") + settingsFolder.mkdirs() + languageFile.writeText(java.util.Locale.getDefault().language) + } + + val update = watchFile(languageFile) + var code by remember(languageFile, update){ mutableStateOf(languageFile.readText().substring(0, 2)) } + remember(code) { + val locale = Locale(code) + java.util.Locale.setDefault(locale) + } + + fun setLocale(locale: java.util.Locale) { + Messages.log("Setting locale to ${locale.language}") + languageFile.writeText(locale.language) + code = locale.language + } + + + val locale = Locale(code, ::setLocale) + remember(code) { Messages.log("Loaded Locale: $code") } + val dir = when(locale["locale.direction"]) { + "rtl" -> LayoutDirection.Rtl + else -> LayoutDirection.Ltr + } - val locale = Locale(languageFile.readText().substring(0, 2)) - CompositionLocalProvider(LocalLocale provides locale) { - content() + CompositionLocalProvider(LocalLayoutDirection provides dir) { + CompositionLocalProvider(LocalLocale provides locale) { + content() + } } } \ No newline at end of file diff --git a/app/test/processing/app/LocaleKtTest.kt b/app/test/processing/app/LocaleKtTest.kt new file mode 100644 index 0000000000..a4e7d637cf --- /dev/null +++ b/app/test/processing/app/LocaleKtTest.kt @@ -0,0 +1,52 @@ +package processing.app + +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.runComposeUiTest +import processing.app.ui.theme.LocalLocale +import processing.app.ui.theme.LocaleProvider +import kotlin.io.path.createTempDirectory +import kotlin.test.Test + +class LocaleKtTest { + @OptIn(ExperimentalTestApi::class) + @Test + fun testLocale() = runComposeUiTest { + val tempPreferencesDir = createTempDirectory("preferences") + + System.setProperty("processing.app.preferences.folder", tempPreferencesDir.toFile().absolutePath) + + setContent { + LocaleProvider { + val locale = LocalLocale.current + Text(locale["menu.file.new"], modifier = Modifier.testTag("localisedText")) + + Button(onClick = { + locale.setLocale(java.util.Locale("es")) + }, modifier = Modifier.testTag("button")) { + Text("Change") + } + } + } + + // Check if usage generates the language file if it doesn't exist + val languageFile = tempPreferencesDir.resolve("language.txt").toFile() + assert(languageFile.exists()) + + // Check if the text is localised + onNodeWithTag("localisedText").assertTextEquals("New") + + // Change the locale to Spanish + onNodeWithTag("button").performClick() + onNodeWithTag("localisedText").assertTextEquals("Nuevo") + + // Check if the preference was saved to file + assert(languageFile.readText().substring(0, 2) == "es") + } +} \ No newline at end of file From 643ec03090d725ab30de2e57970903cb97969193 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 11:42:36 +0200 Subject: [PATCH 20/50] Make setLocale parameter nullable in Locale class Changed the setLocale parameter in the Locale class to be nullable and updated its usage to safely invoke it. This allows for more flexible instantiation when a setLocale function is not required. --- app/src/processing/app/ui/theme/Locale.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 0879418a88..d760998185 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -21,7 +21,7 @@ import java.util.* * val localizedString = locale["someKey"] * ``` */ -class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : Properties() { +class Locale(language: String = "", val setLocale: ((java.util.Locale) -> Unit)? = null) : Properties() { var locale: java.util.Locale = java.util.Locale.getDefault() init { @@ -46,7 +46,7 @@ class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : } operator fun get(key: String): String = getProperty(key, key) fun set(locale: java.util.Locale) { - setLocale(locale) + setLocale?.invoke(locale) } } /** @@ -104,7 +104,7 @@ fun LocaleProvider(content: @Composable () -> Unit) { val update = watchFile(languageFile) var code by remember(languageFile, update){ mutableStateOf(languageFile.readText().substring(0, 2)) } remember(code) { - val locale = Locale(code) + val locale = java.util.Locale(code) java.util.Locale.setDefault(locale) } From 06e309484065b5cb7f31812c9e569db92f073b7c Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 14 Oct 2025 12:49:25 +0200 Subject: [PATCH 21/50] Add compose ui test to the deps --- app/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d3fcbd12d..1aea9ac6b2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ import org.gradle.internal.jvm.Jvm import org.gradle.internal.os.OperatingSystem import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download @@ -119,6 +120,8 @@ dependencies { implementation(libs.markdown) implementation(libs.markdownJVM) + @OptIn(ExperimentalComposeLibrary::class) + testImplementation(compose.uiTest) testImplementation(kotlin("test")) testImplementation(libs.mockitoKotlin) testImplementation(libs.junitJupiter) From d42fb2fe365653bea7e052ba2e2bc660719e00c7 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 11:48:00 +0200 Subject: [PATCH 22/50] Update locale change method in test Replaces the call to locale.setLocale with locale.set in LocaleKtTest to match the updated API for changing the locale. --- app/test/processing/app/LocaleKtTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/test/processing/app/LocaleKtTest.kt b/app/test/processing/app/LocaleKtTest.kt index a4e7d637cf..f8ed32164a 100644 --- a/app/test/processing/app/LocaleKtTest.kt +++ b/app/test/processing/app/LocaleKtTest.kt @@ -28,7 +28,7 @@ class LocaleKtTest { Text(locale["menu.file.new"], modifier = Modifier.testTag("localisedText")) Button(onClick = { - locale.setLocale(java.util.Locale("es")) + locale.set(java.util.Locale("es")) }, modifier = Modifier.testTag("button")) { Text("Change") } From c754e5e9c21f65a9df19c8902a5bc6b893f85ebe Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 13:19:34 +0200 Subject: [PATCH 23/50] Refactor window creation for Compose and Swing Replaces PDEWindow with PDESwingWindow and introduces PDEComposeWindow for better separation of Swing and Compose window creation. Updates Welcome and WelcomeToBeta screens to use new window utilities, improving macOS full window content support and code clarity. --- app/src/processing/app/ui/Welcome.kt | 13 +- app/src/processing/app/ui/WelcomeToBeta.kt | 37 +---- app/src/processing/app/ui/theme/Window.kt | 152 +++++++++++++-------- 3 files changed, 104 insertions(+), 98 deletions(-) diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 492410b881..5354eafe87 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -2,7 +2,6 @@ package processing.app.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -23,7 +22,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.min +import androidx.compose.ui.window.application import com.formdev.flatlaf.util.SystemInfo import processing.app.* import processing.app.ui.components.LanguageChip @@ -32,15 +31,13 @@ import processing.app.ui.theme.* import java.awt.Desktop import java.io.IOException import java.net.URI -import java.nio.file.* -import java.util.* import javax.swing.SwingUtilities class Welcome @Throws(IOException::class) constructor(base: Base) { init { SwingUtilities.invokeLater { - PDEWindow("menu.help.welcome", fullWindowContent = true) { + PDESwingWindow("menu.help.welcome", fullWindowContent = true) { CompositionLocalProvider(LocalBase provides base) { welcome() } @@ -266,8 +263,10 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { @JvmStatic fun main(args: Array) { - pdeapplication("menu.help.welcome", fullWindowContent = true) { - welcome() + application { + PDEComposeWindow("menu.help.welcome", fullWindowContent = true, onClose = ::exitApplication) { + welcome() + } } } } diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index 9d862846d2..a12e2cf13c 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -1,55 +1,26 @@ package processing.app.ui -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.MaterialTheme.typography -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposePanel import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.PointerIcon -import androidx.compose.ui.input.pointer.onPointerEvent -import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application -import androidx.compose.ui.window.rememberWindowState -import com.formdev.flatlaf.util.SystemInfo import processing.app.ui.theme.* import com.mikepenz.markdown.compose.Markdown import com.mikepenz.markdown.m2.markdownColor import com.mikepenz.markdown.m2.markdownTypography -import com.mikepenz.markdown.model.MarkdownColors -import com.mikepenz.markdown.model.MarkdownTypography -import processing.app.Preferences import processing.app.Base.getRevision import processing.app.Base.getVersionName import processing.app.ui.theme.LocalLocale -import processing.app.ui.theme.LocalTheme -import processing.app.ui.theme.Locale -import processing.app.ui.theme.ProcessingTheme -import java.awt.Cursor import java.awt.Dimension -import java.awt.event.KeyAdapter -import java.awt.event.KeyEvent -import java.io.InputStream -import java.util.Properties -import javax.swing.JFrame import javax.swing.SwingUtilities @@ -60,7 +31,7 @@ class WelcomeToBeta { @JvmStatic fun showWelcomeToBeta() { SwingUtilities.invokeLater { - PDEWindow("beta.window.title") { + PDESwingWindow("beta.window.title") { welcomeToBeta() } } @@ -124,8 +95,10 @@ class WelcomeToBeta { @JvmStatic fun main(args: Array) { - pdeapplication("beta.window.title") { - welcomeToBeta() + application { + PDEComposeWindow("beta.window.title") { + welcomeToBeta() + } } } } diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index 0cb419332c..6f49843678 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -1,12 +1,11 @@ package processing.app.ui.theme import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme.colors -import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel @@ -14,7 +13,6 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition -import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import com.formdev.flatlaf.util.SystemInfo @@ -24,46 +22,39 @@ import javax.swing.JFrame val LocalWindow = compositionLocalOf { error("No Window Set") } -class PDEWindow(titleKey: String = "", fullWindowContent: Boolean = false, content: @Composable () -> Unit): JFrame(){ +/** + * A utility class to create a new Window with Compose content in a Swing application. + * It sets up the window with some default properties and allows for custom content. + * Use this when creating a Compose based window from Swing. + * + * Usage example: + * ``` + * SwingUtilities.invokeLater { + * PDESwingWindow("menu.help.welcome", fullWindowContent = true) { + * + * } + * } + * ``` + * + * @param titleKey The key for the window title, which will be localized. + * @param fullWindowContent If true, the content will extend into the title bar area on macOS. + * @param content The composable content to be displayed in the window. + */ +class PDESwingWindow(titleKey: String = "", fullWindowContent: Boolean = false, content: @Composable BoxScope.() -> Unit): JFrame(){ init{ - val mac = SystemInfo.isMacFullWindowContentSupported - - rootPane.apply{ - putClientProperty("apple.awt.transparentTitleBar", mac) - putClientProperty("apple.awt.fullWindowContent", mac) - } - + val window = this defaultCloseOperation = DISPOSE_ON_CLOSE ComposePanel().apply { setContent { - CompositionLocalProvider(LocalWindow provides this@PDEWindow) { - ProcessingTheme { - val locale = LocalLocale.current - this@PDEWindow.title = locale[titleKey] - LaunchedEffect(locale) { - this@PDEWindow.pack() - this@PDEWindow.setLocationRelativeTo(null) - } - - Box( - modifier = Modifier - .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) - ) { - content() - - } - } - } + PDEWindowContent(window, titleKey, fullWindowContent, content) } - - this@PDEWindow.add(this) + window.add(this) } - pack() background = java.awt.Color.white setLocationRelativeTo(null) addKeyListener(object : KeyAdapter() { override fun keyPressed(e: KeyEvent) { - if (e.keyCode == KeyEvent.VK_ESCAPE) this@PDEWindow.dispose() + if (e.keyCode == KeyEvent.VK_ESCAPE) window.dispose() } }) isResizable = false @@ -72,35 +63,78 @@ class PDEWindow(titleKey: String = "", fullWindowContent: Boolean = false, conte } } -fun pdeapplication(titleKey: String = "", fullWindowContent: Boolean = false,content: @Composable () -> Unit){ - application { - val windowState = rememberWindowState( - size = DpSize.Unspecified, - position = WindowPosition(Alignment.Center) - ) +/** + * Internal Composable function to set up the window content with theming and localization. + * It also handles macOS specific properties for full window content. + * + * @param window The JFrame instance to be configured. + * @param titleKey The key for the window title, which will be localized. + * @param fullWindowContent If true, the content will extend into the title bar area on macOS. + * @param content The composable content to be displayed in the window. + */ +@Composable +private fun PDEWindowContent(window: JFrame, titleKey: String, fullWindowContent: Boolean = false, content: @Composable BoxScope.() -> Unit){ + val mac = SystemInfo.isMacOS && SystemInfo.isMacFullWindowContentSupported + remember { + window.rootPane.putClientProperty("apple.awt.fullWindowContent", mac && fullWindowContent) + window.rootPane.putClientProperty("apple.awt.transparentTitleBar", mac && fullWindowContent) + } + + CompositionLocalProvider(LocalWindow provides window) { ProcessingTheme { val locale = LocalLocale.current - val mac = SystemInfo.isMacFullWindowContentSupported - Window(onCloseRequest = ::exitApplication, state = windowState, title = locale[titleKey]) { - window.rootPane.apply { - putClientProperty("apple.awt.fullWindowContent", mac) - putClientProperty("apple.awt.transparentTitleBar", mac) - } - LaunchedEffect(locale){ - window.pack() - window.setLocationRelativeTo(null) - } - CompositionLocalProvider(LocalWindow provides window) { - Surface(color = colors.background) { - Box( - modifier = Modifier - .padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp) - ) { - content() - } - } - } + window.title = locale[titleKey] + LaunchedEffect(locale) { + window.pack() + window.setLocationRelativeTo(null) } + + Box(modifier = Modifier.padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp),content = content) } } } + +/** + * A Composable function to create and display a new window with the specified content. + * This function sets up the window state and handles the close request. + * Use this when creating a Compose based window from another Compose context. + * + * Usage example: + * ``` + * PDEComposeWindow("window.title", fullWindowContent = true, onClose = { /* handle close */ }) { + * // Your window content here + * Text("Hello, World!") + * } + * ``` + * + * This will create a new window with the title localized from "window.title" key, + * with content extending into the title bar area on macOS, and a custom close handler. + * + * Fully standalone example: + * ``` + * application { + * PDEComposeWindow("window.title", fullWindowContent = true, onClose = ::exitApplication) { + * // Your window content here + * } + * } + * ``` + * + * @param titleKey The key for the window title, which will be localized. + * @param fullWindowContent If true, the content will extend into the title bar area on + * macOS. + * @param onClose A lambda function to be called when the window is requested to close. + * @param content The composable content to be displayed in the window. + * + * + * + */ +@Composable +fun PDEComposeWindow(titleKey: String, fullWindowContent: Boolean = false, onClose: () -> Unit = {}, content: @Composable BoxScope.() -> Unit){ + val windowState = rememberWindowState( + size = DpSize.Unspecified, + position = WindowPosition(Alignment.Center) + ) + Window(onCloseRequest = onClose, state = windowState, title = "") { + PDEWindowContent(window, titleKey, fullWindowContent, content) + } +} \ No newline at end of file From 7283c9927121e2df812fae8a44f68ddf86a6e2b6 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 22:56:23 +0200 Subject: [PATCH 24/50] Enable automatic release for Maven Central publishing (#1286) Sets 'automaticRelease' to true in the publishToMavenCentral configuration to automate the release process when publishing to Maven Central. --- core/build.gradle.kts | 2 +- java/preprocessor/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8f7211b131..6708e269dc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { } mavenPublishing{ - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true) signAllPublications() pom{ diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts index d58fa3e7b9..6eb71a1242 100644 --- a/java/preprocessor/build.gradle.kts +++ b/java/preprocessor/build.gradle.kts @@ -47,7 +47,7 @@ publishing{ } mavenPublishing{ - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true) // Only sign if signing is set up if(project.hasProperty("signing.keyId") || project.hasProperty("signingInMemoryKey")) From c1d631356b425d3f83cc9113f59326dd8031b896 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 15 Oct 2025 22:57:46 +0200 Subject: [PATCH 25/50] Fix duplicate include for app:utils module (#1285) Removed redundant 'include("app:utils")' statement and added 'app:utils' to the main include block for consistency. --- settings.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f8cb74c7f..7eacb06877 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ include( "core", "core:examples", "app", + "app:utils", "java", "java:preprocessor", "java:libraries:dxf", @@ -11,5 +12,4 @@ include( "java:libraries:pdf", "java:libraries:serial", "java:libraries:svg", -) -include("app:utils") \ No newline at end of file +) \ No newline at end of file From 5e5f7b9fd5b8a56d2643d24845fde4f9d9fe0b04 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 16 Oct 2025 15:25:33 +0200 Subject: [PATCH 26/50] Refactor theme system and migrate to Material3 Replaces the old Theme implementation with ProcessingTheme, introduces new color, shape, and typography definitions, and updates UI components to use Material3. Also updates dependencies and removes obsolete code to support a more flexible and modern theming approach. --- app/build.gradle.kts | 2 +- app/src/processing/app/ui/Welcome.kt | 2 +- app/src/processing/app/ui/theme/Colors.kt | 92 +++-- .../app/ui/theme/ProcessingTheme.kt | 343 ++++++++++++++++++ app/src/processing/app/ui/theme/Shapes.kt | 11 + app/src/processing/app/ui/theme/Theme.kt | 69 ---- app/src/processing/app/ui/theme/Typography.kt | 65 +++- gradle/libs.versions.toml | 2 +- 8 files changed, 481 insertions(+), 105 deletions(-) create mode 100644 app/src/processing/app/ui/theme/ProcessingTheme.kt create mode 100644 app/src/processing/app/ui/theme/Shapes.kt delete mode 100644 app/src/processing/app/ui/theme/Theme.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 23f781985a..95f14daf5a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,7 +108,7 @@ dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material) + implementation(compose.material3) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) diff --git a/app/src/processing/app/ui/Welcome.kt b/app/src/processing/app/ui/Welcome.kt index 5354eafe87..e79d3e749c 100644 --- a/app/src/processing/app/ui/Welcome.kt +++ b/app/src/processing/app/ui/Welcome.kt @@ -53,7 +53,7 @@ class Welcome @Throws(IOException::class) constructor(base: Base) { modifier = Modifier .background( Brush.linearGradient( - colorStops = arrayOf(0f to Color.Transparent, 1f to Color("#C0D7FF".toColorInt())), + colorStops = arrayOf(0f to Color.Transparent, 1f to Color(0xFFC0D7FF)), start = Offset(815f, 0f), end = Offset(815f * 2, 450f) ) diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index efa97d37cc..aaf716f7cf 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -3,31 +3,75 @@ package processing.app.ui.theme import androidx.compose.material.Colors import androidx.compose.ui.graphics.Color +class ProcessingColors{ + companion object{ + val blue = Color(0xFF0251c8) + val lightBlue = Color(0xFF82AFFF) + + val deepBlue = Color(0xFF1e32aa) + val darkBlue = Color(0xFF0F195A) + + val white = Color(0xFFFFFFFF) + val lightGray = Color(0xFFF5F5F5) + val gray = Color(0xFFDBDBDB) + val darkGray = Color(0xFF898989) + val darkerGray = Color(0xFF727070) + val veryDarkGray = Color(0xFF1E1E1E) + val black = Color(0xFF0D0D0D) + + val error = Color(0xFFFF5757) + + val p5Light = Color(0xFFfd9db9) + val p5Mid = Color(0xFFff4077) + val p5Dark = Color(0xFFaf1f42) + + val foundationLight = Color(0xFFd4b2fe) + val foundationMid = Color(0xFF9c4bff) + val foundationDark = Color(0xFF5501a4) + + val downloadInactive = Color(0xFF8890B3) + val downloadBackgroundActive = Color(0x14508BFF) + } +} + val PDELightColors = Colors( - primary = Color("#0F195A".toColorInt()), - primaryVariant = Color("#1F34AB".toColorInt()), - secondary = Color("#82AFFF".toColorInt()), - secondaryVariant = Color("#0468FF".toColorInt()), - background = Color("#FFFFFF".toColorInt()), - surface = Color("#C0D7FF".toColorInt()), - error = Color("#0F195A".toColorInt()), - onPrimary = Color("#FFFFFF".toColorInt()), - onSecondary = Color("#FFFFFF".toColorInt()), - onBackground = Color("#0F195A".toColorInt()), - onSurface = Color("#FFFFFF".toColorInt()), - onError = Color("#0F195A".toColorInt()), + primary = ProcessingColors.blue, + primaryVariant = ProcessingColors.lightBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.deepBlue, + secondaryVariant = ProcessingColors.darkBlue, + onSecondary = ProcessingColors.white, + + background = ProcessingColors.white, + onBackground = ProcessingColors.darkBlue, + + surface = ProcessingColors.lightGray, + onSurface = ProcessingColors.darkerGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, + isLight = true, ) -fun String.toColorInt(): Int { - if (this[0] == '#') { - var color = substring(1).toLong(16) - if (length == 7) { - color = color or 0x00000000ff000000L - } else if (length != 9) { - throw IllegalArgumentException("Unknown color") - } - return color.toInt() - } - throw IllegalArgumentException("Unknown color") -} \ No newline at end of file +val PDEDarkColors = Colors( + primary = ProcessingColors.deepBlue, + primaryVariant = ProcessingColors.darkBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.lightBlue, + secondaryVariant = ProcessingColors.blue, + onSecondary = ProcessingColors.white, + + background = ProcessingColors.veryDarkGray, + onBackground = ProcessingColors.white, + + surface = ProcessingColors.darkerGray, + onSurface = ProcessingColors.lightGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, + + isLight = false, +) diff --git a/app/src/processing/app/ui/theme/ProcessingTheme.kt b/app/src/processing/app/ui/theme/ProcessingTheme.kt new file mode 100644 index 0000000000..fe90fe33d7 --- /dev/null +++ b/app/src/processing/app/ui/theme/ProcessingTheme.kt @@ -0,0 +1,343 @@ +package processing.app.ui.theme + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +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.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Badge +import androidx.compose.material.BadgedBox +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Card +import androidx.compose.material.Checkbox +import androidx.compose.material.Chip +import androidx.compose.material.ChipDefaults +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Divider +import androidx.compose.material.DropdownMenu +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.IconToggleButton +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.LocalRippleConfiguration +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.RadioButton +import androidx.compose.material.RippleConfiguration +import androidx.compose.material.Slider +import androidx.compose.material.Surface +import androidx.compose.material.Switch +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.material.TextField +import androidx.compose.material.TriStateCheckbox +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Map +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.state.ToggleableState +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import processing.app.LocalPreferences +import processing.app.PreferencesProvider +import java.io.InputStream +import java.util.Properties + +class ProcessingTheme(themeFile: String? = "") : Properties() { + init { + load(ClassLoader.getSystemResourceAsStream("theme.txt")) + load(ClassLoader.getSystemResourceAsStream(themeFile ?: "") ?: InputStream.nullInputStream()) + } + fun getColor(key: String): Color { + return Color(getProperty(key).toColorInt()) + } + + fun String.toColorInt(): Int { + if (this[0] == '#') { + var color = substring(1).toLong(16) + if (length == 7) { + color = color or 0x00000000ff000000L + } else if (length != 9) { + throw IllegalArgumentException("Unknown color") + } + return color.toInt() + } + throw IllegalArgumentException("Unknown color") + } +} + +val LocalProcessingTheme = compositionLocalOf { error("No theme provided") } + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ProcessingTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable() () -> Unit +) { + PreferencesProvider { + val preferences = LocalPreferences.current + val theme = ProcessingTheme(preferences.getProperty("theme")) + CompositionLocalProvider(LocalProcessingTheme provides theme) { + LocaleProvider { + MaterialTheme( + colors = if(darkTheme) PDEDarkColors else PDELightColors, + typography = PDETypography, + shapes = PDEShapes + ){ + CompositionLocalProvider( + LocalRippleConfiguration provides RippleConfiguration( + color = MaterialTheme.colors.primary, + ) + ) { + Box(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxSize()) { + Surface( + color = MaterialTheme.colors.background, + contentColor = MaterialTheme.colors.onBackground + ) { + content() + } + } + } + } + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +fun main(){ + application { + val windowState = rememberWindowState( + size = DpSize(800.dp, 600.dp), + position = WindowPosition(Alignment.Center) + ) + var darkTheme by remember { mutableStateOf(false) } + Window(onCloseRequest = ::exitApplication, state = windowState, title = "Processing Theme") { + ProcessingTheme(darkTheme) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Processing Theme Components", style = MaterialTheme.typography.h1) + Card { + Row { + Checkbox(darkTheme, onCheckedChange = { darkTheme = !darkTheme }) + Text( + "Dark Theme", + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + val scrollable = rememberScrollState() + Column( + modifier = Modifier + .verticalScroll(scrollable), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + ComponentPreview("Colors") { + Column { + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary), + onClick = {}) { + Text("Primary", color = MaterialTheme.colors.onPrimary) + } + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primaryVariant), + onClick = {}) { + Text("Primary Variant", color = MaterialTheme.colors.onPrimary) + } + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary), + onClick = {}) { + Text("Secondary", color = MaterialTheme.colors.onSecondary) + } + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondaryVariant), + onClick = {}) { + Text("Secondary Variant", color = MaterialTheme.colors.onSecondary) + } + } + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.background), + onClick = {}) { + Text("Background", color = MaterialTheme.colors.onBackground) + } + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.surface), + onClick = {}) { + Text("Surface", color = MaterialTheme.colors.onSurface) + } + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.error), + onClick = {}) { + Text("Error", color = MaterialTheme.colors.onError) + } + } + } + } + ComponentPreview("Text & Fonts") { + Column { + Text("Heading 1", style = MaterialTheme.typography.h1) + Text("Heading 2", style = MaterialTheme.typography.h2) + Text("Heading 3", style = MaterialTheme.typography.h3) + Text("Heading 4", style = MaterialTheme.typography.h4) + Text("Heading 5", style = MaterialTheme.typography.h5) + Text("Heading 6", style = MaterialTheme.typography.h6) + + Text("Subtitle 1", style = MaterialTheme.typography.subtitle1) + Text("Subtitle 2", style = MaterialTheme.typography.subtitle2) + + Text("Body 1", style = MaterialTheme.typography.body1) + Text("Body 2", style = MaterialTheme.typography.body2) + + Text("Caption", style = MaterialTheme.typography.caption) + Text("Overline", style = MaterialTheme.typography.overline) + } + } + ComponentPreview("Buttons") { + Button(onClick = {}) { + Text("Filled") + } + Button(onClick = {}, enabled = false) { + Text("Disabled") + } + OutlinedButton(onClick = {}) { + Text("Outlined") + } + TextButton(onClick = {}) { + Text("Text") + } + } + ComponentPreview("Icon Buttons") { + IconButton(onClick = {}) { + Icon(Icons.Default.Map, contentDescription = "Icon Button") + } + var state by remember { mutableStateOf(false) } + IconToggleButton(checked = state, onCheckedChange = { state = it }) { + Icon(Icons.Default.Map, contentDescription = "Icon Toggle Button") + } + } + ComponentPreview("Chip") { + Chip(onClick = {}){ + Text("Chip") + } + Chip(onClick = {}, colors = ChipDefaults.outlinedChipColors(), border = ChipDefaults.outlinedBorder){ + Text("Outlined") + } + } + ComponentPreview("Progress Indicator") { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)){ + CircularProgressIndicator() + LinearProgressIndicator() + } + } + ComponentPreview("Radio Button") { + var state by remember { mutableStateOf(true) } + RadioButton(!state, onClick = { state = false }) + RadioButton(state, onClick = { state = true }) + + } + ComponentPreview("Checkbox") { + var state by remember { mutableStateOf(true) } + Checkbox(state, onCheckedChange = { state = it }) + Checkbox(!state, onCheckedChange = { state = !it }) + Checkbox(state, onCheckedChange = {}, enabled = false) + TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) + } + ComponentPreview("Switch") { + var state by remember { mutableStateOf(true) } + Switch(state, onCheckedChange = { state = it }) + } + ComponentPreview("Slider") { + var state by remember { mutableStateOf(0f) } + Slider(state, onValueChange = { state = it }) + + } + ComponentPreview("Badge") { + IconButton(onClick = {}) { + BadgedBox(badge = { Badge() }) { + Icon(Icons.Default.Map, contentDescription = "Icon with Badge") + } + } + } + ComponentPreview("Number Field") { + var number by remember { mutableStateOf("123") } + TextField(number, onValueChange = { + if(it.all { char -> char.isDigit() }) { + number = it + } + }, label = { Text("Number Field") }) + + } + ComponentPreview("Text Field") { + Row { + var text by remember { mutableStateOf("Text Field") } + TextField(text, onValueChange = { text = it }) + } + var text by remember { mutableStateOf("Outlined Text Field") } + OutlinedTextField(text, onValueChange = { text = it}) + } + ComponentPreview("Dropdown Menu") { + var show by remember { mutableStateOf(false) } + Chip(onClick = { + show = true + }){ + Text("Dropdown Menu") + } + DropdownMenu( + expanded = show, + onDismissRequest = { + show = false + }, + ) { + Text("Menu Item 1", modifier = Modifier.padding(8.dp)) + Text("Menu Item 2", modifier = Modifier.padding(8.dp)) + Text("Menu Item 3", modifier = Modifier.padding(8.dp)) + } + + } + + ComponentPreview("Scrollable View") { + + } + + ComponentPreview("Tabs") { + + } + } + } + } + } + } +} + +@Composable +private fun ComponentPreview(title: String, content: @Composable () -> Unit) { + Column { + Text(title, style = MaterialTheme.typography.h4) + Divider() + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(vertical = 8.dp)) { + content() + } + Divider() + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Shapes.kt b/app/src/processing/app/ui/theme/Shapes.kt new file mode 100644 index 0000000000..581b235593 --- /dev/null +++ b/app/src/processing/app/ui/theme/Shapes.kt @@ -0,0 +1,11 @@ +package processing.app.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val PDEShapes = Shapes( + small = RoundedCornerShape(30.dp), + medium = RoundedCornerShape(6.dp), + large = RoundedCornerShape(6.dp) +) \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt deleted file mode 100644 index db12aab0f9..0000000000 --- a/app/src/processing/app/ui/theme/Theme.kt +++ /dev/null @@ -1,69 +0,0 @@ -package processing.app.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.graphics.Color -import processing.app.LocalPreferences -import processing.app.PreferencesProvider -import java.io.InputStream -import java.util.Properties - - -// TODO: Other monospace font -// TODO: Look at flatlaf components -// TODO: Look at Jetpack Compose default components -// TODO: Message Boxes -// TODO: Tabs -// TODO: https://web.mit.edu/6.005/www/sp14/psets/ps4/java-6-tutorial/components.html - -class Theme(themeFile: String? = "") : Properties() { - init { - load(ClassLoader.getSystemResourceAsStream("theme.txt")) - load(ClassLoader.getSystemResourceAsStream(themeFile ?: "") ?: InputStream.nullInputStream()) - } - fun getColor(key: String): Color { - return Color(getProperty(key).toColorInt()) - } -} - -val LocalTheme = compositionLocalOf { error("No theme provided") } - -@Composable -fun ProcessingTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable() () -> Unit -) { - PreferencesProvider { - val preferences = LocalPreferences.current - val theme = Theme(preferences.getProperty("theme")) -// val colors = Colors( -// primary = theme.getColor("editor.gradient.top"), -// primaryVariant = theme.getColor("toolbar.button.pressed.field"), -// secondary = theme.getColor("editor.gradient.bottom"), -// secondaryVariant = theme.getColor("editor.scrollbar.thumb.pressed.color"), -// background = theme.getColor("editor.bgcolor"), -// surface = theme.getColor("editor.bgcolor"), -// error = theme.getColor("status.error.bgcolor"), -// onPrimary = theme.getColor("toolbar.button.enabled.field"), -// onSecondary = theme.getColor("toolbar.button.enabled.field"), -// onBackground = theme.getColor("editor.fgcolor"), -// onSurface = theme.getColor("editor.fgcolor"), -// onError = theme.getColor("status.error.fgcolor"), -// isLight = theme.getProperty("laf.mode").equals("light") -// ) - - - CompositionLocalProvider(LocalTheme provides theme) { - LocaleProvider { - MaterialTheme( - colors = if(darkTheme) PDELightColors else PDELightColors, - typography = Typography, - content = content - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Typography.kt b/app/src/processing/app/ui/theme/Typography.kt index c21d554f7e..81e26ef426 100644 --- a/app/src/processing/app/ui/theme/Typography.kt +++ b/app/src/processing/app/ui/theme/Typography.kt @@ -1,9 +1,6 @@ package processing.app.ui.theme -import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Typography -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle @@ -46,16 +43,66 @@ val spaceGroteskFont = FontFamily( ) ) -val Typography = Typography( +val PDETypography = Typography( defaultFontFamily = spaceGroteskFont, - h4 = TextStyle( + h1 = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 42.725.sp, + lineHeight = 48.sp + ), + h2 = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 34.18.sp, + lineHeight = 40.sp + ), + h3 = TextStyle( fontWeight = FontWeight.Bold, - fontSize = 19.sp, - lineHeight = 24.sp + fontSize = 27.344.sp, + lineHeight = 32.sp + ), + h4 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 21.875.sp, + lineHeight = 28.sp + ), + h5 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 17.5.sp, + lineHeight = 22.sp + ), + h6 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 18.sp ), body1 = TextStyle( fontWeight = FontWeight.Normal, - fontSize = 15.sp, - lineHeight = 19.sp + fontSize = 14.sp, + lineHeight = 18.sp + ), + body2 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 12.8.sp, + lineHeight = 16.sp + ), + subtitle1 = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 20.sp + ), + subtitle2 = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 13.824.sp, + lineHeight = 16.sp, + ), + caption = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 11.2.sp, + lineHeight = 14.sp ), + overline = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 8.96.sp, + lineHeight = 10.sp + ) ) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 050502f4ca..f402f9a306 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.0.20" -compose-plugin = "1.7.1" +compose-plugin = "1.9.1" jogl = "2.5.0" antlr = "4.13.2" jupiter = "5.12.0" From 0406f6ac200ed40822f7a8a17cabb8f4ed529b29 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 16 Oct 2025 16:33:40 +0200 Subject: [PATCH 27/50] Migrate UI components to Material3 Updated dependencies and code to use Material3 components from androidx.compose.material3. Replaced Material and legacy Compose UI elements with their Material3 equivalents in ProcessingTheme and component previews. Updated Kotlin version and added material3 library to dependencies. --- app/build.gradle.kts | 3 +- .../app/ui/theme/ProcessingTheme.kt | 95 +++++++++++-------- build.gradle.kts | 1 - gradle/libs.versions.toml | 4 +- 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 95f14daf5a..6c8ac55f00 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,6 +17,7 @@ plugins{ alias(libs.plugins.compose.compiler) alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.serialization) alias(libs.plugins.download) } @@ -108,13 +109,13 @@ dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material3) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) implementation(compose.materialIconsExtended) implementation(compose.desktop.currentOs) + implementation(libs.material3) implementation(libs.compottie) implementation(libs.kaml) diff --git a/app/src/processing/app/ui/theme/ProcessingTheme.kt b/app/src/processing/app/ui/theme/ProcessingTheme.kt index fe90fe33d7..67610432f0 100644 --- a/app/src/processing/app/ui/theme/ProcessingTheme.kt +++ b/app/src/processing/app/ui/theme/ProcessingTheme.kt @@ -1,6 +1,7 @@ package processing.app.ui.theme import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -21,7 +22,9 @@ import androidx.compose.material.ChipDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.FilterChip import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.IconToggleButton @@ -99,22 +102,24 @@ fun ProcessingTheme( val theme = ProcessingTheme(preferences.getProperty("theme")) CompositionLocalProvider(LocalProcessingTheme provides theme) { LocaleProvider { - MaterialTheme( - colors = if(darkTheme) PDEDarkColors else PDELightColors, - typography = PDETypography, - shapes = PDEShapes - ){ - CompositionLocalProvider( - LocalRippleConfiguration provides RippleConfiguration( - color = MaterialTheme.colors.primary, - ) + androidx.compose.material3.MaterialTheme{ + MaterialTheme( + colors = if (darkTheme) PDEDarkColors else PDELightColors, + typography = PDETypography, + shapes = PDEShapes ) { - Box(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxSize()) { - Surface( - color = MaterialTheme.colors.background, - contentColor = MaterialTheme.colors.onBackground - ) { - content() + CompositionLocalProvider( + LocalRippleConfiguration provides RippleConfiguration( + color = MaterialTheme.colors.primary, + ) + ) { + Box(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxSize()) { + Surface( + color = MaterialTheme.colors.background, + contentColor = MaterialTheme.colors.onBackground + ) { + content() + } } } } @@ -214,6 +219,9 @@ fun main(){ } } ComponentPreview("Buttons") { + androidx.compose.material3.Button(onClick = {}) { + Text("Material3 Button") + } Button(onClick = {}) { Text("Filled") } @@ -231,10 +239,6 @@ fun main(){ IconButton(onClick = {}) { Icon(Icons.Default.Map, contentDescription = "Icon Button") } - var state by remember { mutableStateOf(false) } - IconToggleButton(checked = state, onCheckedChange = { state = it }) { - Icon(Icons.Default.Map, contentDescription = "Icon Toggle Button") - } } ComponentPreview("Chip") { Chip(onClick = {}){ @@ -243,33 +247,39 @@ fun main(){ Chip(onClick = {}, colors = ChipDefaults.outlinedChipColors(), border = ChipDefaults.outlinedBorder){ Text("Outlined") } + FilterChip(selected = false, onClick = {}){ + Text("Filter not Selected") + } + FilterChip(selected = true, onClick = {}){ + Text("Filter Selected") + } } ComponentPreview("Progress Indicator") { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)){ - CircularProgressIndicator() - LinearProgressIndicator() + androidx.compose.material3.CircularProgressIndicator() + androidx.compose.material3.LinearProgressIndicator() } } ComponentPreview("Radio Button") { var state by remember { mutableStateOf(true) } - RadioButton(!state, onClick = { state = false }) - RadioButton(state, onClick = { state = true }) + androidx.compose.material3.RadioButton(!state, onClick = { state = false }) + androidx.compose.material3.RadioButton(state, onClick = { state = true }) } ComponentPreview("Checkbox") { var state by remember { mutableStateOf(true) } - Checkbox(state, onCheckedChange = { state = it }) - Checkbox(!state, onCheckedChange = { state = !it }) - Checkbox(state, onCheckedChange = {}, enabled = false) - TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) + androidx.compose.material3.Checkbox(state, onCheckedChange = { state = it }) + androidx.compose.material3.Checkbox(!state, onCheckedChange = { state = !it }) + androidx.compose.material3.Checkbox(state, onCheckedChange = {}, enabled = false) + androidx.compose.material3.TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) } ComponentPreview("Switch") { var state by remember { mutableStateOf(true) } - Switch(state, onCheckedChange = { state = it }) + androidx.compose.material3.Switch(state, onCheckedChange = { state = it }) } ComponentPreview("Slider") { var state by remember { mutableStateOf(0f) } - Slider(state, onValueChange = { state = it }) + androidx.compose.material3.Slider(state, onValueChange = { state = it }) } ComponentPreview("Badge") { @@ -291,29 +301,36 @@ fun main(){ ComponentPreview("Text Field") { Row { var text by remember { mutableStateOf("Text Field") } - TextField(text, onValueChange = { text = it }) + androidx.compose.material3.TextField(text, onValueChange = { text = it }) } var text by remember { mutableStateOf("Outlined Text Field") } - OutlinedTextField(text, onValueChange = { text = it}) + androidx.compose.material3.OutlinedTextField(text, onValueChange = { text = it}) } ComponentPreview("Dropdown Menu") { var show by remember { mutableStateOf(false) } - Chip(onClick = { - show = true - }){ - Text("Dropdown Menu") - } + TextField("Dropdown", onValueChange = {}, readOnly = true, modifier = Modifier + .padding(8.dp) + .background(Color.Transparent) + .clickable { show = true } + ) DropdownMenu( expanded = show, onDismissRequest = { show = false }, ) { - Text("Menu Item 1", modifier = Modifier.padding(8.dp)) - Text("Menu Item 2", modifier = Modifier.padding(8.dp)) - Text("Menu Item 3", modifier = Modifier.padding(8.dp)) + DropdownMenuItem(onClick = { show = false }) { + Text("Menu Item 1", modifier = Modifier.padding(8.dp)) + } + DropdownMenuItem(onClick = { show = false }) { + Text("Menu Item 2", modifier = Modifier.padding(8.dp)) + } + DropdownMenuItem(onClick = { show = false }) { + Text("Menu Item 3", modifier = Modifier.padding(8.dp)) + } } + } ComponentPreview("Scrollable View") { diff --git a/build.gradle.kts b/build.gradle.kts index 8e7ad44a7a..6c8c5262cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ plugins { kotlin("jvm") version libs.versions.kotlin apply false - alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.jetbrainsCompose) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f402f9a306..b1cafb2d2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.0.20" +kotlin = "2.2.20" compose-plugin = "1.9.1" jogl = "2.5.0" antlr = "4.13.2" @@ -35,10 +35,10 @@ markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" } clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" } kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } +material3 = { module = "org.jetbrains.compose.material3:material3", version = "1.9.0" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } download = { id = "de.undercouch.download", version = "5.6.0" } From d3681f38c63d2353334c41994473911521827afd Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 16 Oct 2025 18:24:51 +0200 Subject: [PATCH 28/50] Remove ContributionManager and ContributionPane UI files (#1276) Deleted ContributionManager.kt and ContributionPane.kt from the contrib/ui directory. This removes the Compose-based contributions manager and its detail pane prototypes which got merged unnecessarily --- .../app/contrib/ui/ContributionManager.kt | 310 ------------------ .../app/contrib/ui/ContributionPane.kt | 79 ----- 2 files changed, 389 deletions(-) delete mode 100644 app/src/processing/app/contrib/ui/ContributionManager.kt delete mode 100644 app/src/processing/app/contrib/ui/ContributionPane.kt diff --git a/app/src/processing/app/contrib/ui/ContributionManager.kt b/app/src/processing/app/contrib/ui/ContributionManager.kt deleted file mode 100644 index 2ad472159b..0000000000 --- a/app/src/processing/app/contrib/ui/ContributionManager.kt +++ /dev/null @@ -1,310 +0,0 @@ -package processing.app.contrib.ui - -import androidx.compose.animation.Animatable -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposePanel -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerIcon -import androidx.compose.ui.input.pointer.pointerHoverIcon -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import com.charleskorn.kaml.Yaml -import com.charleskorn.kaml.YamlConfiguration -import kotlinx.serialization.Serializable -import processing.app.Platform -import processing.app.loadPreferences -import java.net.URL -import java.util.* -import javax.swing.JFrame -import javax.swing.SwingUtilities -import kotlin.io.path.* - - -fun main() = application { - Window(onCloseRequest = ::exitApplication) { - contributionsManager() - } -} - -enum class Status { - VALID, - BROKEN, - DEPRECATED -} -enum class Type { - library, - mode, - tool, - examples, -} - -@Serializable -data class Author( - val name: String, - val url: String? = null, -) - -@Serializable -data class Contribution( - val id: Int, - val status: Status, - val source: String, - val type: Type, - val name: String? = null, - val categories: List? = emptyList(), - val authors: String? = null, - val authorList: List? = emptyList(), - val url: String? = null, - val sentence: String? = null, - val paragraph: String? = null, - val version: String? = null, - val prettyVersion: String? = null, - val minRevision: Int? = null, - val maxRevision: Int? = null, - val download: String? = null, - val isUpdate: Boolean? = null, - val isInstalled: Boolean? = null, -) - -@Serializable -data class Contributions( - val contributions: List -) - -fun openContributionsManager(){ - // open the compose window - - SwingUtilities.invokeLater { - val frame = JFrame("Contributions Manager") - frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE - frame.setSize(800, 600) - - val composePanel = ComposePanel() - composePanel.setContent { - contributionsManager() - } - - frame.contentPane.add(composePanel) - frame.isVisible = true - } -} - -@Composable -fun contributionsManager(){ - var contributions by remember { mutableStateOf(listOf()) } - var localContributions by remember { mutableStateOf(listOf()) } - var error by remember { mutableStateOf(null) } - - val preferences = loadPreferences() - - LaunchedEffect(preferences){ - try { - localContributions = loadContributionProperties(preferences) - .map { (type, props) -> - Contribution( - id = 0, - status = Status.VALID, - source = "local", - type = type, - name = props.getProperty("name"), - authors = props.getProperty("authors"), - url = props.getProperty("url"), - sentence = props.getProperty("sentence"), - paragraph = props.getProperty("paragraph"), - version = props.getProperty("version"), - prettyVersion = props.getProperty("prettyVersion"), - minRevision = props.getProperty("minRevision")?.toIntOrNull(), - maxRevision = props.getProperty("maxRevision")?.toIntOrNull(), - download = props.getProperty("download"), - ) - } - } catch (e: Exception){ - error = e - } - } - - - LaunchedEffect(Unit){ - try { - val url = URL("https://github.com/mingness/processing-contributions-new/raw/refs/heads/main/contributions.yaml") - val connection = url.openConnection() - val inputStream = connection.getInputStream() - val yaml = inputStream.readAllBytes().decodeToString() - // TODO cache yaml in processing folder - - val parser = Yaml( - configuration = YamlConfiguration( - strictMode = false - ) - ) - val result = parser.decodeFromString(Contributions.serializer(), yaml) - - contributions = result.contributions - .filter { it.status == Status.VALID } - .map { - // TODO Parse better - val authorList = it.authors?.split(",")?.map { author -> - val parts = author.split("](") - val name = parts[0].removePrefix("[") - val url = parts.getOrNull(1)?.removeSuffix(")") - Author(name, url) - } ?: emptyList() - it.copy(authorList = authorList) - } - } catch (e: Exception){ - error = e - } - } - if(error != null){ - Text("Error loading contributions: ${error?.message}") - return - } - if(contributions.isEmpty()){ - Text("Loading contributions...") - return - } - - val contributionsByType = (contributions + localContributions) - .groupBy { it.name } - .map { (_, contributions) -> - if(contributions.size == 1) return@map contributions.first() - else{ - // check if they all have the same version, otherwise return the newest version - val versions = contributions.mapNotNull { it.version } - if(versions.toSet().size == 1) return@map contributions.first().copy(isInstalled = true) - else{ - val newest = contributions.maxByOrNull { it.version?.toIntOrNull() ?: 0 } - if(newest != null) return@map newest.copy(isUpdate = true, isInstalled = true) - else return@map contributions.first().copy(isUpdate = true, isInstalled = true) - } - } - } - .groupBy { it.type } - - val types = Type.entries - var selectedType by remember { mutableStateOf(types.first()) } - val contributionsForType = (contributionsByType[selectedType] ?: emptyList()) - .sortedBy { it.name } - - var selectedContribution by remember { mutableStateOf(null) } - Box{ - Column { - Row{ - for(type in types){ - val background = remember { Animatable(Color.Transparent) } - val color = remember { Animatable(Color.Black) } - LaunchedEffect(selectedType){ - if(selectedType == type){ - background.animateTo(Color(0xff0251c8)) - color.animateTo(Color.White) - }else{ - background.animateTo(Color.Transparent) - color.animateTo(Color.Black) - } - } - - Row(modifier = Modifier - .background(background.value) - .pointerHoverIcon(PointerIcon.Hand) - .clickable { - selectedType = type - selectedContribution = null - } - .padding(16.dp, 8.dp) - ){ - Text(type.name, color = color.value) - val updates = contributionsByType[type]?.count { it.isUpdate == true } ?: 0 - if(updates > 0){ - Text("($updates)") - } - } - } - } - - Box(modifier = Modifier.weight(1f)){ - val state = rememberLazyListState() - LazyColumn(state = state) { - item{ - // Table Header - } - items(contributionsForType){ contribution -> - Row(modifier = Modifier - .pointerHoverIcon(PointerIcon.Hand) - .clickable { selectedContribution = contribution } - .padding(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Row(modifier = Modifier.weight(1f)){ - if(contribution.isUpdate == true){ - Text("Update") - }else if(contribution.isInstalled == true){ - Text("Installed") - } - - } - Row(horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.weight(8f)){ - Text(contribution.name ?: "Unnamed", fontWeight = FontWeight.Bold) - Text(contribution.sentence ?: "No description", maxLines = 1, overflow = TextOverflow.Ellipsis) - } - Row(modifier = Modifier.weight(4f)){ - Text(contribution.authorList?.joinToString { it.name } ?: "Unknown") - } - } - } - } - VerticalScrollbar( - modifier = Modifier - .align(Alignment.CenterEnd) - .background(Color.LightGray) - .fillMaxHeight(), - adapter = rememberScrollbarAdapter( - scrollState = state - ) - ) - } - ContributionPane( - contribution = selectedContribution, - onClose = { selectedContribution = null } - ) - } - - } - -} - - -fun loadContributionProperties(preferences: Properties): List>{ - val result = mutableListOf>() - val sketchBook = Path(preferences.getProperty("sketchbook.path.four", Platform.getDefaultSketchbookFolder().path)) - sketchBook.forEachDirectoryEntry{ contributionsFolder -> - if(!contributionsFolder.isDirectory()) return@forEachDirectoryEntry - val typeName = contributionsFolder.fileName.toString() - val type: Type = when(typeName){ - "libraries" -> Type.library - "modes" -> Type.mode - "tools" -> Type.tool - "examples" -> Type.examples - else -> return@forEachDirectoryEntry - } - contributionsFolder.forEachDirectoryEntry { contribution -> - if(!contribution.isDirectory()) return@forEachDirectoryEntry - contribution.forEachDirectoryEntry("*.properties"){ entry -> - val props = Properties() - props.load(entry.inputStream()) - result += Pair(type, props) - } - } - } - return result -} \ No newline at end of file diff --git a/app/src/processing/app/contrib/ui/ContributionPane.kt b/app/src/processing/app/contrib/ui/ContributionPane.kt deleted file mode 100644 index 2f4a96931b..0000000000 --- a/app/src/processing/app/contrib/ui/ContributionPane.kt +++ /dev/null @@ -1,79 +0,0 @@ -package processing.app.contrib.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.pointer.PointerIcon -import androidx.compose.ui.input.pointer.pointerHoverIcon -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Window - -//--processing-blue-light: #82afff; -//--processing-blue-mid: #0564ff; -//--processing-blue-deep: #1e32aa; -//--processing-blue-dark: #0f195a; -//--processing-blue: #0251c8; - -@Composable -fun ContributionPane(contribution: Contribution?, onClose: () -> Unit) { - if(contribution == null) { - return - } - val typeName = when(contribution.type) { - Type.library -> "Library" - Type.tool -> "Tool" - Type.examples -> "Example" - Type.mode -> "Mode" - } - Window( - title = "${typeName}: ${contribution.name}", - onCloseRequest = onClose, - onKeyEvent = { - if(it.key == Key.Escape) { - onClose() - true - } else { - false - } - } - ){ - Box { - Column(modifier = Modifier.padding(10.dp)) { - Text(typeName, style = TextStyle(fontSize = 16.sp)) - Text(contribution.name ?: "", style = TextStyle(fontSize = 20.sp)) - Row(modifier = Modifier.padding(0.dp, 10.dp)) { - val action = when(contribution.isUpdate) { - true -> "Update" - false, null -> when(contribution.isInstalled) { - true -> "Uninstall" - false, null -> "Install" - } - } - Text(action, - style = TextStyle(fontSize = 14.sp, color = Color.White), - modifier = Modifier - .clickable { - - } - .pointerHoverIcon(PointerIcon.Hand) - .background(Color(0xff0251c8)) - .padding(24.dp,12.dp) - ) - } - Text(contribution.paragraph ?: "", style = TextStyle(fontSize = 14.sp)) - } - } - } - -} \ No newline at end of file From bf4d163c2eca0edd5fd3f3ee532275e2c8a73c3d Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 17 Oct 2025 03:56:35 +0200 Subject: [PATCH 29/50] Refactor Locale class and add LocaleProvider test (#1283) * Refactor Locale class and add LocaleProvider test * Make setLocale parameter nullable in Locale class Changed the setLocale parameter in the Locale class to be nullable and updated its usage to safely invoke it. This allows for more flexible instantiation when a setLocale function is not required. * Add compose ui test to the deps * Update locale change method in test Replaces the call to locale.setLocale with locale.set in LocaleKtTest to match the updated API for changing the locale. --- app/build.gradle.kts | 3 + app/src/processing/app/ui/theme/Locale.kt | 129 ++++++++++++++++++---- app/test/processing/app/LocaleKtTest.kt | 52 +++++++++ 3 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 app/test/processing/app/LocaleKtTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d3fcbd12d..1aea9ac6b2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ import org.gradle.internal.jvm.Jvm import org.gradle.internal.os.OperatingSystem import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download @@ -119,6 +120,8 @@ dependencies { implementation(libs.markdown) implementation(libs.markdownJVM) + @OptIn(ExperimentalComposeLibrary::class) + testImplementation(compose.uiTest) testImplementation(kotlin("test")) testImplementation(libs.mockitoKotlin) testImplementation(libs.junitJupiter) diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 254c0946c1..d760998185 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -1,24 +1,41 @@ package processing.app.ui.theme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import processing.app.LocalPreferences -import processing.app.Messages -import processing.app.Platform -import processing.app.PlatformStart -import processing.app.watchFile +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection +import processing.app.* import java.io.File import java.io.InputStream import java.util.* -class Locale(language: String = "") : Properties() { +/** + * The Locale class extends the standard Java Properties class + * to provide localization capabilities. + * It loads localization resources from property files based on the specified language code. + * The class also provides a method to change the current locale and update the application accordingly. + * Usage: + * ``` + * val locale = Locale("es") { newLocale -> + * // Handle locale change, e.g., update UI or restart application + * } + * val localizedString = locale["someKey"] + * ``` + */ +class Locale(language: String = "", val setLocale: ((java.util.Locale) -> Unit)? = null) : Properties() { + var locale: java.util.Locale = java.util.Locale.getDefault() + init { - val locale = java.util.Locale.getDefault() - load(ClassLoader.getSystemResourceAsStream("PDE.properties")) - load(ClassLoader.getSystemResourceAsStream("PDE_${locale.language}.properties") ?: InputStream.nullInputStream()) - load(ClassLoader.getSystemResourceAsStream("PDE_${locale.toLanguageTag()}.properties") ?: InputStream.nullInputStream()) - load(ClassLoader.getSystemResourceAsStream("PDE_${language}.properties") ?: InputStream.nullInputStream()) + loadResourceUTF8("PDE.properties") + loadResourceUTF8("PDE_${locale.language}.properties") + loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties") + loadResourceUTF8("PDE_${language}.properties") + } + + fun loadResourceUTF8(path: String) { + val stream = ClassLoader.getSystemResourceAsStream(path) + stream?.reader(charset = Charsets.UTF_8)?.use { reader -> + load(reader) + } } @Deprecated("Use get instead", ReplaceWith("get(key)")) @@ -28,18 +45,86 @@ class Locale(language: String = "") : Properties() { return value } operator fun get(key: String): String = getProperty(key, key) + fun set(locale: java.util.Locale) { + setLocale?.invoke(locale) + } } -val LocalLocale = compositionLocalOf { Locale() } +/** + * A CompositionLocal to provide access to the Locale instance + * throughout the composable hierarchy. see [LocaleProvider] + * Usage: + * ``` + * val locale = LocalLocale.current + * val localizedString = locale["someKey"] + * ``` + */ +val LocalLocale = compositionLocalOf { error("No Locale Set") } + +/** + * This composable function sets up a locale provider that manages application localization. + * It initializes the locale from a language file, watches for changes to that file, and updates + * the locale accordingly. It uses a [Locale] class to handle loading of localized resources. + * + * Usage: + * ``` + * LocaleProvider { + * // Your app content here + * } + * ``` + * + * To access the locale: + * ``` + * val locale = LocalLocale.current + * val localizedString = locale["someKey"] + * ``` + * + * To change the locale: + * ``` + * locale.set(java.util.Locale("es")) + * ``` + * This will update the `language.txt` file and reload the locale. + */ @Composable fun LocaleProvider(content: @Composable () -> Unit) { - PlatformStart() + val preferencesFolderOverride: File? = System.getProperty("processing.app.preferences.folder")?.let { File(it) } + + val settingsFolder = preferencesFolderOverride ?: remember{ + Platform.init() + Platform.getSettingsFolder() + } + val languageFile = settingsFolder.resolve("language.txt") + remember(languageFile){ + if(languageFile.exists()) return@remember - val settingsFolder = Platform.getSettingsFolder() - val languageFile = File(settingsFolder, "language.txt") - watchFile(languageFile) + Messages.log("Creating language file at ${languageFile.absolutePath}") + settingsFolder.mkdirs() + languageFile.writeText(java.util.Locale.getDefault().language) + } + + val update = watchFile(languageFile) + var code by remember(languageFile, update){ mutableStateOf(languageFile.readText().substring(0, 2)) } + remember(code) { + val locale = java.util.Locale(code) + java.util.Locale.setDefault(locale) + } + + fun setLocale(locale: java.util.Locale) { + Messages.log("Setting locale to ${locale.language}") + languageFile.writeText(locale.language) + code = locale.language + } + + + val locale = Locale(code, ::setLocale) + remember(code) { Messages.log("Loaded Locale: $code") } + val dir = when(locale["locale.direction"]) { + "rtl" -> LayoutDirection.Rtl + else -> LayoutDirection.Ltr + } - val locale = Locale(languageFile.readText().substring(0, 2)) - CompositionLocalProvider(LocalLocale provides locale) { - content() + CompositionLocalProvider(LocalLayoutDirection provides dir) { + CompositionLocalProvider(LocalLocale provides locale) { + content() + } } } \ No newline at end of file diff --git a/app/test/processing/app/LocaleKtTest.kt b/app/test/processing/app/LocaleKtTest.kt new file mode 100644 index 0000000000..f8ed32164a --- /dev/null +++ b/app/test/processing/app/LocaleKtTest.kt @@ -0,0 +1,52 @@ +package processing.app + +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.runComposeUiTest +import processing.app.ui.theme.LocalLocale +import processing.app.ui.theme.LocaleProvider +import kotlin.io.path.createTempDirectory +import kotlin.test.Test + +class LocaleKtTest { + @OptIn(ExperimentalTestApi::class) + @Test + fun testLocale() = runComposeUiTest { + val tempPreferencesDir = createTempDirectory("preferences") + + System.setProperty("processing.app.preferences.folder", tempPreferencesDir.toFile().absolutePath) + + setContent { + LocaleProvider { + val locale = LocalLocale.current + Text(locale["menu.file.new"], modifier = Modifier.testTag("localisedText")) + + Button(onClick = { + locale.set(java.util.Locale("es")) + }, modifier = Modifier.testTag("button")) { + Text("Change") + } + } + } + + // Check if usage generates the language file if it doesn't exist + val languageFile = tempPreferencesDir.resolve("language.txt").toFile() + assert(languageFile.exists()) + + // Check if the text is localised + onNodeWithTag("localisedText").assertTextEquals("New") + + // Change the locale to Spanish + onNodeWithTag("button").performClick() + onNodeWithTag("localisedText").assertTextEquals("Nuevo") + + // Check if the preference was saved to file + assert(languageFile.readText().substring(0, 2) == "es") + } +} \ No newline at end of file From 4ea9640696e3f036921aba3576e019b09a396f67 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 20 Oct 2025 10:32:05 +0200 Subject: [PATCH 30/50] Remove material3 theme references --- .../app/ui/theme/ProcessingTheme.kt | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/app/src/processing/app/ui/theme/ProcessingTheme.kt b/app/src/processing/app/ui/theme/ProcessingTheme.kt index 67610432f0..2b9d52ddbb 100644 --- a/app/src/processing/app/ui/theme/ProcessingTheme.kt +++ b/app/src/processing/app/ui/theme/ProcessingTheme.kt @@ -102,7 +102,7 @@ fun ProcessingTheme( val theme = ProcessingTheme(preferences.getProperty("theme")) CompositionLocalProvider(LocalProcessingTheme provides theme) { LocaleProvider { - androidx.compose.material3.MaterialTheme{ + MaterialTheme{ MaterialTheme( colors = if (darkTheme) PDEDarkColors else PDELightColors, typography = PDETypography, @@ -219,9 +219,6 @@ fun main(){ } } ComponentPreview("Buttons") { - androidx.compose.material3.Button(onClick = {}) { - Text("Material3 Button") - } Button(onClick = {}) { Text("Filled") } @@ -256,30 +253,30 @@ fun main(){ } ComponentPreview("Progress Indicator") { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)){ - androidx.compose.material3.CircularProgressIndicator() - androidx.compose.material3.LinearProgressIndicator() + CircularProgressIndicator() + LinearProgressIndicator() } } ComponentPreview("Radio Button") { var state by remember { mutableStateOf(true) } - androidx.compose.material3.RadioButton(!state, onClick = { state = false }) - androidx.compose.material3.RadioButton(state, onClick = { state = true }) + RadioButton(!state, onClick = { state = false }) + RadioButton(state, onClick = { state = true }) } ComponentPreview("Checkbox") { var state by remember { mutableStateOf(true) } - androidx.compose.material3.Checkbox(state, onCheckedChange = { state = it }) - androidx.compose.material3.Checkbox(!state, onCheckedChange = { state = !it }) - androidx.compose.material3.Checkbox(state, onCheckedChange = {}, enabled = false) - androidx.compose.material3.TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) + Checkbox(state, onCheckedChange = { state = it }) + Checkbox(!state, onCheckedChange = { state = !it }) + Checkbox(state, onCheckedChange = {}, enabled = false) + TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) } ComponentPreview("Switch") { var state by remember { mutableStateOf(true) } - androidx.compose.material3.Switch(state, onCheckedChange = { state = it }) + Switch(state, onCheckedChange = { state = it }) } ComponentPreview("Slider") { var state by remember { mutableStateOf(0f) } - androidx.compose.material3.Slider(state, onValueChange = { state = it }) + Slider(state, onValueChange = { state = it }) } ComponentPreview("Badge") { @@ -301,10 +298,10 @@ fun main(){ ComponentPreview("Text Field") { Row { var text by remember { mutableStateOf("Text Field") } - androidx.compose.material3.TextField(text, onValueChange = { text = it }) + TextField(text, onValueChange = { text = it }) } var text by remember { mutableStateOf("Outlined Text Field") } - androidx.compose.material3.OutlinedTextField(text, onValueChange = { text = it}) + OutlinedTextField(text, onValueChange = { text = it}) } ComponentPreview("Dropdown Menu") { var show by remember { mutableStateOf(false) } From 62702d4ebbc6af7a45bc249429264fb7e70accba Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 20 Oct 2025 11:42:15 +0200 Subject: [PATCH 31/50] Add Material3 theme support and PDETheme composable Introduces Material3 color schemes (PDE3LightColor, PDE3DarkColor) and typography (PDE3Typography) for the Processing app. Adds a new PDETheme composable and demo in Theme.kt, deprecates legacy Material2 theme usage, and updates theme-related files to support Material3 components and styles. --- app/src/processing/app/ui/theme/Colors.kt | 45 +++ .../app/ui/theme/ProcessingTheme.kt | 34 +- app/src/processing/app/ui/theme/Theme.kt | 304 ++++++++++++++++++ app/src/processing/app/ui/theme/Typography.kt | 18 ++ 4 files changed, 383 insertions(+), 18 deletions(-) create mode 100644 app/src/processing/app/ui/theme/Theme.kt diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index aaf716f7cf..cd6ff75b1e 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -1,6 +1,9 @@ package processing.app.ui.theme import androidx.compose.material.Colors +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.ui.graphics.Color class ProcessingColors{ @@ -34,6 +37,7 @@ class ProcessingColors{ } } +@Deprecated("Use PDE3LightColor instead") val PDELightColors = Colors( primary = ProcessingColors.blue, primaryVariant = ProcessingColors.lightBlue, @@ -55,6 +59,7 @@ val PDELightColors = Colors( isLight = true, ) +@Deprecated("Use PDE3DarkColor instead") val PDEDarkColors = Colors( primary = ProcessingColors.deepBlue, primaryVariant = ProcessingColors.darkBlue, @@ -75,3 +80,43 @@ val PDEDarkColors = Colors( isLight = false, ) + +val PDE3LightColor = lightColorScheme( + primary = ProcessingColors.blue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.deepBlue, + onSecondary = ProcessingColors.white, + + tertiary = ProcessingColors.lightBlue, + onTertiary = ProcessingColors.white, + + background = ProcessingColors.white, + onBackground = ProcessingColors.darkBlue, + + surface = ProcessingColors.lightGray, + onSurface = ProcessingColors.darkerGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, +) + +val PDE3DarkColor = darkColorScheme( + primary = ProcessingColors.deepBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.lightBlue, + onSecondary = ProcessingColors.white, + + tertiary = ProcessingColors.blue, + onTertiary = ProcessingColors.white, + + background = ProcessingColors.veryDarkGray, + onBackground = ProcessingColors.white, + + surface = ProcessingColors.darkerGray, + onSurface = ProcessingColors.lightGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, +) \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/ProcessingTheme.kt b/app/src/processing/app/ui/theme/ProcessingTheme.kt index 2b9d52ddbb..4fab37a371 100644 --- a/app/src/processing/app/ui/theme/ProcessingTheme.kt +++ b/app/src/processing/app/ui/theme/ProcessingTheme.kt @@ -91,7 +91,7 @@ class ProcessingTheme(themeFile: String? = "") : Properties() { val LocalProcessingTheme = compositionLocalOf { error("No theme provided") } -@OptIn(ExperimentalMaterialApi::class) +@Deprecated("Use PDETheme instead", ReplaceWith("PDETheme")) @Composable fun ProcessingTheme( darkTheme: Boolean = isSystemInDarkTheme(), @@ -102,24 +102,22 @@ fun ProcessingTheme( val theme = ProcessingTheme(preferences.getProperty("theme")) CompositionLocalProvider(LocalProcessingTheme provides theme) { LocaleProvider { - MaterialTheme{ - MaterialTheme( - colors = if (darkTheme) PDEDarkColors else PDELightColors, - typography = PDETypography, - shapes = PDEShapes + MaterialTheme( + colors = if (darkTheme) PDEDarkColors else PDELightColors, + typography = PDETypography, + shapes = PDEShapes + ) { + CompositionLocalProvider( + LocalRippleConfiguration provides RippleConfiguration( + color = MaterialTheme.colors.primary, + ) ) { - CompositionLocalProvider( - LocalRippleConfiguration provides RippleConfiguration( - color = MaterialTheme.colors.primary, - ) - ) { - Box(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxSize()) { - Surface( - color = MaterialTheme.colors.background, - contentColor = MaterialTheme.colors.onBackground - ) { - content() - } + Box(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxSize()) { + Surface( + color = MaterialTheme.colors.background, + contentColor = MaterialTheme.colors.onBackground + ) { + content() } } } diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt new file mode 100644 index 0000000000..801c42b217 --- /dev/null +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -0,0 +1,304 @@ +package processing.app.ui.theme + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +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.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Map +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.FilterChip +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Slider +import androidx.compose.material3.Switch +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TriStateCheckbox +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.state.ToggleableState +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import processing.app.PreferencesProvider + +@Composable +fun PDETheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +){ + PreferencesProvider { + LocaleProvider { + MaterialTheme( + colorScheme = if(darkTheme) PDE3DarkColor else PDE3LightColor, + typography = PDE3Typography + ){ + Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.onBackground, + LocalDensity provides Density(1.25f, 1.25f), + content = content + ) + } + } + } + } +} + +fun main() { + application { + val windowState = rememberWindowState( + size = DpSize(800.dp, 600.dp), + position = WindowPosition(Alignment.Center) + ) + var darkTheme by remember { mutableStateOf(false) } + Window(onCloseRequest = ::exitApplication, state = windowState, title = "Processing Theme") { + PDETheme(darkTheme) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Processing Theme Components", style = MaterialTheme.typography.titleLarge) + Card { + Row { + Checkbox(darkTheme, onCheckedChange = { darkTheme = !darkTheme }) + Text( + "Dark Theme", + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + val scrollable = rememberScrollState() + Column( + modifier = Modifier + .verticalScroll(scrollable), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + ComponentPreview("Colors") { + Column { + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), + onClick = {}) { + Text("Primary", color = MaterialTheme.colorScheme.onPrimary) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary), + onClick = {}) { + Text("Secondary", color = MaterialTheme.colorScheme.onSecondary) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiary), + onClick = {}) { + Text("Tertiary", color = MaterialTheme.colorScheme.onTertiary) + } + } + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background), + onClick = {}) { + Text("Background", color = MaterialTheme.colorScheme.onBackground) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surface), + onClick = {}) { + Text("Surface", color = MaterialTheme.colorScheme.onSurface) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), + onClick = {}) { + Text("Error", color = MaterialTheme.colorScheme.onError) + } + } + } + } + ComponentPreview("Text & Fonts") { + Column { + Text("displayLarge", style = MaterialTheme.typography.displayLarge) + Text("displayMedium", style = MaterialTheme.typography.displayMedium) + Text("displaySmall", style = MaterialTheme.typography.displaySmall) + + Text("headlineLarge", style = MaterialTheme.typography.headlineLarge) + Text("headlineMedium", style = MaterialTheme.typography.headlineMedium) + Text("headlineSmall", style = MaterialTheme.typography.headlineSmall) + + Text("titleLarge", style = MaterialTheme.typography.titleLarge) + Text("titleMedium", style = MaterialTheme.typography.titleMedium) + Text("titleSmall", style = MaterialTheme.typography.titleSmall) + + Text("bodyLarge", style = MaterialTheme.typography.bodyLarge) + Text("bodyMedium", style = MaterialTheme.typography.bodyMedium) + Text("bodySmall", style = MaterialTheme.typography.bodySmall) + + Text("labelLarge", style = MaterialTheme.typography.labelLarge) + Text("labelMedium", style = MaterialTheme.typography.labelMedium) + Text("labelSmall", style = MaterialTheme.typography.labelSmall) + } + } + ComponentPreview("Buttons") { + Button(onClick = {}) { + Text("Filled") + } + Button(onClick = {}, enabled = false) { + Text("Disabled") + } + OutlinedButton(onClick = {}) { + Text("Outlined") + } + TextButton(onClick = {}) { + Text("Text") + } + } + ComponentPreview("Icon Buttons") { + IconButton(onClick = {}) { + Icon(Icons.Default.Map, contentDescription = "Icon Button") + } + } + ComponentPreview("Chip") { + AssistChip(onClick = {}, label = { + Text("Assist Chip") + }) + FilterChip(selected = false, onClick = {}, label = { + Text("Filter not Selected") + }) + FilterChip(selected = true, onClick = {}, label = { + Text("Filter Selected") + }) + } + ComponentPreview("Progress Indicator") { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)){ + CircularProgressIndicator() + LinearProgressIndicator() + } + } + ComponentPreview("Radio Button") { + var state by remember { mutableStateOf(true) } + RadioButton(!state, onClick = { state = false }) + RadioButton(state, onClick = { state = true }) + + } + ComponentPreview("Checkbox") { + var state by remember { mutableStateOf(true) } + Checkbox(state, onCheckedChange = { state = it }) + Checkbox(!state, onCheckedChange = { state = !it }) + Checkbox(state, onCheckedChange = {}, enabled = false) + TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) + } + ComponentPreview("Switch") { + var state by remember { mutableStateOf(true) } + Switch(state, onCheckedChange = { state = it }) + } + ComponentPreview("Slider") { + var state by remember { mutableStateOf(0f) } + Slider(state, onValueChange = { state = it }) + + } + ComponentPreview("Badge") { + IconButton(onClick = {}) { + BadgedBox(badge = { Badge() }) { + Icon(Icons.Default.Map, contentDescription = "Icon with Badge") + } + } + } + ComponentPreview("Number Field") { + var number by remember { mutableStateOf("123") } + TextField(number, onValueChange = { + if(it.all { char -> char.isDigit() }) { + number = it + } + }, label = { Text("Number Field") }) + + } + ComponentPreview("Text Field") { + Row { + var text by remember { mutableStateOf("Text Field") } + TextField(text, onValueChange = { text = it }) + } + var text by remember { mutableStateOf("Outlined Text Field") } + OutlinedTextField(text, onValueChange = { text = it}) + } + ComponentPreview("Dropdown Menu") { + var show by remember { mutableStateOf(false) } + AssistChip( + onClick = { show = true }, + label = { Text("Show Menu") } + ) + DropdownMenu( + expanded = show, + onDismissRequest = { + show = false + }, + ) { + DropdownMenuItem(onClick = { show = false }, text = { + Text("Menu Item 1", modifier = Modifier.padding(8.dp)) + }) + DropdownMenuItem(onClick = { show = false }, text = { + Text("Menu Item 2", modifier = Modifier.padding(8.dp)) + }) + DropdownMenuItem(onClick = { show = false }, text = { + Text("Menu Item 3", modifier = Modifier.padding(8.dp)) + }) + } + + + } + + ComponentPreview("Scrollable View") { + + } + + ComponentPreview("Tabs") { + + } + } + } + } + } + } +} + +@Composable +private fun ComponentPreview(title: String, content: @Composable () -> Unit) { + Column { + Text(title, style = MaterialTheme.typography.titleLarge) + HorizontalDivider() + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(vertical = 8.dp)) { + content() + } + HorizontalDivider() + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Typography.kt b/app/src/processing/app/ui/theme/Typography.kt index 81e26ef426..1d1c139f1c 100644 --- a/app/src/processing/app/ui/theme/Typography.kt +++ b/app/src/processing/app/ui/theme/Typography.kt @@ -105,4 +105,22 @@ val PDETypography = Typography( fontSize = 8.96.sp, lineHeight = 10.sp ) +) +val base = androidx.compose.material3.Typography() +val PDE3Typography = androidx.compose.material3.Typography( + displayLarge = base.displayLarge.copy(fontFamily = spaceGroteskFont), + displayMedium = base.displayMedium.copy(fontFamily = spaceGroteskFont), + displaySmall = base.displaySmall.copy(fontFamily = spaceGroteskFont), + headlineLarge = base.headlineLarge.copy(fontFamily = spaceGroteskFont), + headlineMedium = base.headlineMedium.copy(fontFamily = spaceGroteskFont), + headlineSmall = base.headlineSmall.copy(fontFamily = spaceGroteskFont), + titleLarge = base.titleLarge.copy(fontFamily = spaceGroteskFont), + titleMedium = base.titleMedium.copy(fontFamily = spaceGroteskFont), + titleSmall = base.titleSmall.copy(fontFamily = spaceGroteskFont), + bodyLarge = base.bodyLarge.copy(fontFamily = spaceGroteskFont), + bodyMedium = base.bodyMedium.copy(fontFamily = spaceGroteskFont), + bodySmall = base.bodySmall.copy(fontFamily = spaceGroteskFont), + labelLarge = base.labelLarge.copy(fontFamily = spaceGroteskFont), + labelMedium = base.labelMedium.copy(fontFamily = spaceGroteskFont), + labelSmall = base.labelSmall.copy(fontFamily = spaceGroteskFont), ) \ No newline at end of file From 02af9b3a2b9d6e36c4a06ba0c3f61428310a60d7 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 20 Oct 2025 12:02:32 +0200 Subject: [PATCH 32/50] Add color containers to theme and update previews Introduces primary, secondary, tertiary, and error container colors to the color scheme in Colors.kt and updates Theme.kt to preview these new color containers in the UI. Also adds a disabled Switch preview and adjusts the initial Slider value for better demonstration. --- app/src/processing/app/ui/theme/Colors.kt | 15 +++++++++++- app/src/processing/app/ui/theme/Theme.kt | 30 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index cd6ff75b1e..0a619b406f 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -23,6 +23,7 @@ class ProcessingColors{ val black = Color(0xFF0D0D0D) val error = Color(0xFFFF5757) + val errorContainer = Color(0xFFFFA6A6) val p5Light = Color(0xFFfd9db9) val p5Mid = Color(0xFFff4077) @@ -85,12 +86,21 @@ val PDE3LightColor = lightColorScheme( primary = ProcessingColors.blue, onPrimary = ProcessingColors.white, + primaryContainer = ProcessingColors.downloadBackgroundActive, + onPrimaryContainer = ProcessingColors.darkBlue, + secondary = ProcessingColors.deepBlue, onSecondary = ProcessingColors.white, - tertiary = ProcessingColors.lightBlue, + secondaryContainer = ProcessingColors.downloadInactive, + onSecondaryContainer = ProcessingColors.white, + + tertiary = ProcessingColors.p5Mid, onTertiary = ProcessingColors.white, + tertiaryContainer = ProcessingColors.p5Light, + onTertiaryContainer = ProcessingColors.p5Dark, + background = ProcessingColors.white, onBackground = ProcessingColors.darkBlue, @@ -99,6 +109,9 @@ val PDE3LightColor = lightColorScheme( error = ProcessingColors.error, onError = ProcessingColors.white, + + errorContainer = ProcessingColors.errorContainer, + onErrorContainer = ProcessingColors.white ) val PDE3DarkColor = darkColorScheme( diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 801c42b217..581cf30419 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -127,6 +127,28 @@ fun main() { Text("Tertiary", color = MaterialTheme.colorScheme.onTertiary) } } + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primaryContainer), + onClick = {}) { + Text("Primary Container", color = MaterialTheme.colorScheme.onPrimaryContainer) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer), + onClick = {}) { + Text("Secondary Container", color = MaterialTheme.colorScheme.onSecondaryContainer) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer), + onClick = {}) { + Text("Tertiary Container", color = MaterialTheme.colorScheme.onTertiaryContainer) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.errorContainer), + onClick = {}) { + Text("Error Container", color = MaterialTheme.colorScheme.onErrorContainer) + } + } Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { Button( colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background), @@ -138,6 +160,11 @@ fun main() { onClick = {}) { Text("Surface", color = MaterialTheme.colorScheme.onSurface) } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), + onClick = {}) { + Text("Surface Variant", color = MaterialTheme.colorScheme.onSurfaceVariant) + } Button( colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), onClick = {}) { @@ -221,9 +248,10 @@ fun main() { ComponentPreview("Switch") { var state by remember { mutableStateOf(true) } Switch(state, onCheckedChange = { state = it }) + Switch(!state, enabled = false, onCheckedChange = { state = it }) } ComponentPreview("Slider") { - var state by remember { mutableStateOf(0f) } + var state by remember { mutableStateOf(0.5f) } Slider(state, onValueChange = { state = it }) } From 27efb0be969c5390464ad37248153990d169fd65 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 20 Oct 2025 13:23:57 +0200 Subject: [PATCH 33/50] Update color schemes to Material Theme Builder output Replaces the previous PDE3LightColor and PDE3DarkColor with new lightScheme and darkScheme generated by Material Theme Builder, including support for medium and high contrast variants. Updates Theme.kt to use the new color scheme variables, improving consistency and maintainability of theme colors. --- app/src/processing/app/ui/theme/Colors.kt | 487 ++++++++++++++++++++-- app/src/processing/app/ui/theme/Theme.kt | 2 +- 2 files changed, 444 insertions(+), 45 deletions(-) diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index 0a619b406f..5d74df0ed9 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -82,54 +82,453 @@ val PDEDarkColors = Colors( isLight = false, ) -val PDE3LightColor = lightColorScheme( - primary = ProcessingColors.blue, - onPrimary = ProcessingColors.white, - - primaryContainer = ProcessingColors.downloadBackgroundActive, - onPrimaryContainer = ProcessingColors.darkBlue, - - secondary = ProcessingColors.deepBlue, - onSecondary = ProcessingColors.white, - - secondaryContainer = ProcessingColors.downloadInactive, - onSecondaryContainer = ProcessingColors.white, - - tertiary = ProcessingColors.p5Mid, - onTertiary = ProcessingColors.white, - - tertiaryContainer = ProcessingColors.p5Light, - onTertiaryContainer = ProcessingColors.p5Dark, - - background = ProcessingColors.white, - onBackground = ProcessingColors.darkBlue, - - surface = ProcessingColors.lightGray, - onSurface = ProcessingColors.darkerGray, - - error = ProcessingColors.error, - onError = ProcessingColors.white, - - errorContainer = ProcessingColors.errorContainer, - onErrorContainer = ProcessingColors.white +/* + * Generated using Material Theme Builder + * https://material-foundation.github.io/material-theme-builder/ +*/ + +val primaryLight = Color(0xFF003B98) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFF0251C8) +val onPrimaryContainerLight = Color(0xFFC1D0FF) +val secondaryLight = Color(0xFF00188B) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFF1E32AA) +val onSecondaryContainerLight = Color(0xFF9DA9FF) +val tertiaryLight = Color(0xFFB8004A) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFDE2360) +val onTertiaryContainerLight = Color(0xFFFFFBFF) +val errorLight = Color(0xFFB6212A) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFF5757) +val onErrorContainerLight = Color(0xFF5E000A) +val backgroundLight = Color(0xFFFAF8FF) +val onBackgroundLight = Color(0xFF191B23) +val surfaceLight = Color(0xFFFCF8F8) +val onSurfaceLight = Color(0xFF1C1B1B) +val surfaceVariantLight = Color(0xFFE0E3E3) +val onSurfaceVariantLight = Color(0xFF444748) +val outlineLight = Color(0xFF747878) +val outlineVariantLight = Color(0xFFC4C7C7) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF313030) +val inverseOnSurfaceLight = Color(0xFFF4F0EF) +val inversePrimaryLight = Color(0xFFB2C5FF) +val surfaceDimLight = Color(0xFFDDD9D9) +val surfaceBrightLight = Color(0xFFFCF8F8) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFF7F3F2) +val surfaceContainerLight = Color(0xFFF1EDEC) +val surfaceContainerHighLight = Color(0xFFEBE7E7) +val surfaceContainerHighestLight = Color(0xFFE5E2E1) + +val primaryLightMediumContrast = Color(0xFF00317F) +val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) +val primaryContainerLightMediumContrast = Color(0xFF0251C8) +val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val secondaryLightMediumContrast = Color(0xFF00188B) +val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) +val secondaryContainerLightMediumContrast = Color(0xFF1E32AA) +val onSecondaryContainerLightMediumContrast = Color(0xFFD2D6FF) +val tertiaryLightMediumContrast = Color(0xFF71002A) +val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightMediumContrast = Color(0xFFD31859) +val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val errorLightMediumContrast = Color(0xFF73000F) +val onErrorLightMediumContrast = Color(0xFFFFFFFF) +val errorContainerLightMediumContrast = Color(0xFFCB3136) +val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) +val backgroundLightMediumContrast = Color(0xFFFAF8FF) +val onBackgroundLightMediumContrast = Color(0xFF191B23) +val surfaceLightMediumContrast = Color(0xFFFCF8F8) +val onSurfaceLightMediumContrast = Color(0xFF111111) +val surfaceVariantLightMediumContrast = Color(0xFFE0E3E3) +val onSurfaceVariantLightMediumContrast = Color(0xFF333737) +val outlineLightMediumContrast = Color(0xFF4F5354) +val outlineVariantLightMediumContrast = Color(0xFF6A6E6E) +val scrimLightMediumContrast = Color(0xFF000000) +val inverseSurfaceLightMediumContrast = Color(0xFF313030) +val inverseOnSurfaceLightMediumContrast = Color(0xFFF4F0EF) +val inversePrimaryLightMediumContrast = Color(0xFFB2C5FF) +val surfaceDimLightMediumContrast = Color(0xFFC9C6C5) +val surfaceBrightLightMediumContrast = Color(0xFFFCF8F8) +val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightMediumContrast = Color(0xFFF7F3F2) +val surfaceContainerLightMediumContrast = Color(0xFFEBE7E7) +val surfaceContainerHighLightMediumContrast = Color(0xFFE0DCDB) +val surfaceContainerHighestLightMediumContrast = Color(0xFFD4D1D0) + +val primaryLightHighContrast = Color(0xFF00276A) +val onPrimaryLightHighContrast = Color(0xFFFFFFFF) +val primaryContainerLightHighContrast = Color(0xFF0042A7) +val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) +val secondaryLightHighContrast = Color(0xFF00188B) +val onSecondaryLightHighContrast = Color(0xFFFFFFFF) +val secondaryContainerLightHighContrast = Color(0xFF1E32AA) +val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) +val tertiaryLightHighContrast = Color(0xFF5E0022) +val onTertiaryLightHighContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightHighContrast = Color(0xFF95003A) +val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) +val errorLightHighContrast = Color(0xFF60000B) +val onErrorLightHighContrast = Color(0xFFFFFFFF) +val errorContainerLightHighContrast = Color(0xFF970117) +val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) +val backgroundLightHighContrast = Color(0xFFFAF8FF) +val onBackgroundLightHighContrast = Color(0xFF191B23) +val surfaceLightHighContrast = Color(0xFFFCF8F8) +val onSurfaceLightHighContrast = Color(0xFF000000) +val surfaceVariantLightHighContrast = Color(0xFFE0E3E3) +val onSurfaceVariantLightHighContrast = Color(0xFF000000) +val outlineLightHighContrast = Color(0xFF292D2D) +val outlineVariantLightHighContrast = Color(0xFF464A4A) +val scrimLightHighContrast = Color(0xFF000000) +val inverseSurfaceLightHighContrast = Color(0xFF313030) +val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) +val inversePrimaryLightHighContrast = Color(0xFFB2C5FF) +val surfaceDimLightHighContrast = Color(0xFFBBB8B7) +val surfaceBrightLightHighContrast = Color(0xFFFCF8F8) +val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightHighContrast = Color(0xFFF4F0EF) +val surfaceContainerLightHighContrast = Color(0xFFE5E2E1) +val surfaceContainerHighLightHighContrast = Color(0xFFD7D4D3) +val surfaceContainerHighestLightHighContrast = Color(0xFFC9C6C5) + +val primaryDark = Color(0xFFB2C5FF) +val onPrimaryDark = Color(0xFF002B73) +val primaryContainerDark = Color(0xFF0251C8) +val onPrimaryContainerDark = Color(0xFFC1D0FF) +val secondaryDark = Color(0xFFBBC3FF) +val onSecondaryDark = Color(0xFF001B96) +val secondaryContainerDark = Color(0xFF1E32AA) +val onSecondaryContainerDark = Color(0xFF9DA9FF) +val tertiaryDark = Color(0xFFFFB2BE) +val onTertiaryDark = Color(0xFF660026) +val tertiaryContainerDark = Color(0xFFFF4E7D) +val onTertiaryContainerDark = Color(0xFF52001C) +val errorDark = Color(0xFFFFB3AF) +val onErrorDark = Color(0xFF68000D) +val errorContainerDark = Color(0xFFFF5757) +val onErrorContainerDark = Color(0xFF5E000A) +val backgroundDark = Color(0xFF11131A) +val onBackgroundDark = Color(0xFFE1E2EC) +val surfaceDark = Color(0xFF141313) +val onSurfaceDark = Color(0xFFE5E2E1) +val surfaceVariantDark = Color(0xFF444748) +val onSurfaceVariantDark = Color(0xFFC4C7C7) +val outlineDark = Color(0xFF8E9192) +val outlineVariantDark = Color(0xFF444748) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFE5E2E1) +val inverseOnSurfaceDark = Color(0xFF313030) +val inversePrimaryDark = Color(0xFF1256CD) +val surfaceDimDark = Color(0xFF141313) +val surfaceBrightDark = Color(0xFF3A3939) +val surfaceContainerLowestDark = Color(0xFF0E0E0E) +val surfaceContainerLowDark = Color(0xFF1C1B1B) +val surfaceContainerDark = Color(0xFF201F1F) +val surfaceContainerHighDark = Color(0xFF2A2A2A) +val surfaceContainerHighestDark = Color(0xFF353434) + +val primaryDarkMediumContrast = Color(0xFFD1DBFF) +val onPrimaryDarkMediumContrast = Color(0xFF00215D) +val primaryContainerDarkMediumContrast = Color(0xFF5C8CFF) +val onPrimaryContainerDarkMediumContrast = Color(0xFF000000) +val secondaryDarkMediumContrast = Color(0xFFD7DAFF) +val onSecondaryDarkMediumContrast = Color(0xFF00147A) +val secondaryContainerDarkMediumContrast = Color(0xFF7587FE) +val onSecondaryContainerDarkMediumContrast = Color(0xFF000000) +val tertiaryDarkMediumContrast = Color(0xFFFFD1D7) +val onTertiaryDarkMediumContrast = Color(0xFF52001D) +val tertiaryContainerDarkMediumContrast = Color(0xFFFF4E7D) +val onTertiaryContainerDarkMediumContrast = Color(0xFF000000) +val errorDarkMediumContrast = Color(0xFFFFD2CE) +val onErrorDarkMediumContrast = Color(0xFF540008) +val errorContainerDarkMediumContrast = Color(0xFFFF5757) +val onErrorContainerDarkMediumContrast = Color(0xFF000000) +val backgroundDarkMediumContrast = Color(0xFF11131A) +val onBackgroundDarkMediumContrast = Color(0xFFE1E2EC) +val surfaceDarkMediumContrast = Color(0xFF141313) +val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkMediumContrast = Color(0xFF444748) +val onSurfaceVariantDarkMediumContrast = Color(0xFFDADDDD) +val outlineDarkMediumContrast = Color(0xFFAFB2B3) +val outlineVariantDarkMediumContrast = Color(0xFF8D9191) +val scrimDarkMediumContrast = Color(0xFF000000) +val inverseSurfaceDarkMediumContrast = Color(0xFFE5E2E1) +val inverseOnSurfaceDarkMediumContrast = Color(0xFF2A2A2A) +val inversePrimaryDarkMediumContrast = Color(0xFF0041A4) +val surfaceDimDarkMediumContrast = Color(0xFF141313) +val surfaceBrightDarkMediumContrast = Color(0xFF454444) +val surfaceContainerLowestDarkMediumContrast = Color(0xFF070707) +val surfaceContainerLowDarkMediumContrast = Color(0xFF1E1D1D) +val surfaceContainerDarkMediumContrast = Color(0xFF282827) +val surfaceContainerHighDarkMediumContrast = Color(0xFF333232) +val surfaceContainerHighestDarkMediumContrast = Color(0xFF3E3D3D) + +val primaryDarkHighContrast = Color(0xFFEDEFFF) +val onPrimaryDarkHighContrast = Color(0xFF000000) +val primaryContainerDarkHighContrast = Color(0xFFACC1FF) +val onPrimaryContainerDarkHighContrast = Color(0xFF000926) +val secondaryDarkHighContrast = Color(0xFFEFEEFF) +val onSecondaryDarkHighContrast = Color(0xFF000000) +val secondaryContainerDarkHighContrast = Color(0xFFB6BEFF) +val onSecondaryContainerDarkHighContrast = Color(0xFF000434) +val tertiaryDarkHighContrast = Color(0xFFFFEBED) +val onTertiaryDarkHighContrast = Color(0xFF000000) +val tertiaryContainerDarkHighContrast = Color(0xFFFFACB9) +val onTertiaryContainerDarkHighContrast = Color(0xFF210007) +val errorDarkHighContrast = Color(0xFFFFECEA) +val onErrorDarkHighContrast = Color(0xFF000000) +val errorContainerDarkHighContrast = Color(0xFFFFAEA9) +val onErrorContainerDarkHighContrast = Color(0xFF220001) +val backgroundDarkHighContrast = Color(0xFF11131A) +val onBackgroundDarkHighContrast = Color(0xFFE1E2EC) +val surfaceDarkHighContrast = Color(0xFF141313) +val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkHighContrast = Color(0xFF444748) +val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF) +val outlineDarkHighContrast = Color(0xFFEEF0F1) +val outlineVariantDarkHighContrast = Color(0xFFC0C3C4) +val scrimDarkHighContrast = Color(0xFF000000) +val inverseSurfaceDarkHighContrast = Color(0xFFE5E2E1) +val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) +val inversePrimaryDarkHighContrast = Color(0xFF0041A4) +val surfaceDimDarkHighContrast = Color(0xFF141313) +val surfaceBrightDarkHighContrast = Color(0xFF51504F) +val surfaceContainerLowestDarkHighContrast = Color(0xFF000000) +val surfaceContainerLowDarkHighContrast = Color(0xFF201F1F) +val surfaceContainerDarkHighContrast = Color(0xFF313030) +val surfaceContainerHighDarkHighContrast = Color(0xFF3C3B3B) +val surfaceContainerHighestDarkHighContrast = Color(0xFF484646) + + +val lightScheme = lightColorScheme( + primary = primaryLight, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, + secondary = secondaryLight, + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = tertiaryLight, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = backgroundLight, + onBackground = onBackgroundLight, + surface = surfaceLight, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, + surfaceDim = surfaceDimLight, + surfaceBright = surfaceBrightLight, + surfaceContainerLowest = surfaceContainerLowestLight, + surfaceContainerLow = surfaceContainerLowLight, + surfaceContainer = surfaceContainerLight, + surfaceContainerHigh = surfaceContainerHighLight, + surfaceContainerHighest = surfaceContainerHighestLight, ) -val PDE3DarkColor = darkColorScheme( - primary = ProcessingColors.deepBlue, - onPrimary = ProcessingColors.white, +val darkScheme = darkColorScheme( + primary = primaryDark, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, + secondary = secondaryDark, + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = tertiaryDark, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = backgroundDark, + onBackground = onBackgroundDark, + surface = surfaceDark, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, + surfaceDim = surfaceDimDark, + surfaceBright = surfaceBrightDark, + surfaceContainerLowest = surfaceContainerLowestDark, + surfaceContainerLow = surfaceContainerLowDark, + surfaceContainer = surfaceContainerDark, + surfaceContainerHigh = surfaceContainerHighDark, + surfaceContainerHighest = surfaceContainerHighestDark, +) - secondary = ProcessingColors.lightBlue, - onSecondary = ProcessingColors.white, +private val mediumContrastLightColorScheme = lightColorScheme( + primary = primaryLightMediumContrast, + onPrimary = onPrimaryLightMediumContrast, + primaryContainer = primaryContainerLightMediumContrast, + onPrimaryContainer = onPrimaryContainerLightMediumContrast, + secondary = secondaryLightMediumContrast, + onSecondary = onSecondaryLightMediumContrast, + secondaryContainer = secondaryContainerLightMediumContrast, + onSecondaryContainer = onSecondaryContainerLightMediumContrast, + tertiary = tertiaryLightMediumContrast, + onTertiary = onTertiaryLightMediumContrast, + tertiaryContainer = tertiaryContainerLightMediumContrast, + onTertiaryContainer = onTertiaryContainerLightMediumContrast, + error = errorLightMediumContrast, + onError = onErrorLightMediumContrast, + errorContainer = errorContainerLightMediumContrast, + onErrorContainer = onErrorContainerLightMediumContrast, + background = backgroundLightMediumContrast, + onBackground = onBackgroundLightMediumContrast, + surface = surfaceLightMediumContrast, + onSurface = onSurfaceLightMediumContrast, + surfaceVariant = surfaceVariantLightMediumContrast, + onSurfaceVariant = onSurfaceVariantLightMediumContrast, + outline = outlineLightMediumContrast, + outlineVariant = outlineVariantLightMediumContrast, + scrim = scrimLightMediumContrast, + inverseSurface = inverseSurfaceLightMediumContrast, + inverseOnSurface = inverseOnSurfaceLightMediumContrast, + inversePrimary = inversePrimaryLightMediumContrast, + surfaceDim = surfaceDimLightMediumContrast, + surfaceBright = surfaceBrightLightMediumContrast, + surfaceContainerLowest = surfaceContainerLowestLightMediumContrast, + surfaceContainerLow = surfaceContainerLowLightMediumContrast, + surfaceContainer = surfaceContainerLightMediumContrast, + surfaceContainerHigh = surfaceContainerHighLightMediumContrast, + surfaceContainerHighest = surfaceContainerHighestLightMediumContrast, +) - tertiary = ProcessingColors.blue, - onTertiary = ProcessingColors.white, +private val highContrastLightColorScheme = lightColorScheme( + primary = primaryLightHighContrast, + onPrimary = onPrimaryLightHighContrast, + primaryContainer = primaryContainerLightHighContrast, + onPrimaryContainer = onPrimaryContainerLightHighContrast, + secondary = secondaryLightHighContrast, + onSecondary = onSecondaryLightHighContrast, + secondaryContainer = secondaryContainerLightHighContrast, + onSecondaryContainer = onSecondaryContainerLightHighContrast, + tertiary = tertiaryLightHighContrast, + onTertiary = onTertiaryLightHighContrast, + tertiaryContainer = tertiaryContainerLightHighContrast, + onTertiaryContainer = onTertiaryContainerLightHighContrast, + error = errorLightHighContrast, + onError = onErrorLightHighContrast, + errorContainer = errorContainerLightHighContrast, + onErrorContainer = onErrorContainerLightHighContrast, + background = backgroundLightHighContrast, + onBackground = onBackgroundLightHighContrast, + surface = surfaceLightHighContrast, + onSurface = onSurfaceLightHighContrast, + surfaceVariant = surfaceVariantLightHighContrast, + onSurfaceVariant = onSurfaceVariantLightHighContrast, + outline = outlineLightHighContrast, + outlineVariant = outlineVariantLightHighContrast, + scrim = scrimLightHighContrast, + inverseSurface = inverseSurfaceLightHighContrast, + inverseOnSurface = inverseOnSurfaceLightHighContrast, + inversePrimary = inversePrimaryLightHighContrast, + surfaceDim = surfaceDimLightHighContrast, + surfaceBright = surfaceBrightLightHighContrast, + surfaceContainerLowest = surfaceContainerLowestLightHighContrast, + surfaceContainerLow = surfaceContainerLowLightHighContrast, + surfaceContainer = surfaceContainerLightHighContrast, + surfaceContainerHigh = surfaceContainerHighLightHighContrast, + surfaceContainerHighest = surfaceContainerHighestLightHighContrast, +) - background = ProcessingColors.veryDarkGray, - onBackground = ProcessingColors.white, +private val mediumContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkMediumContrast, + onPrimary = onPrimaryDarkMediumContrast, + primaryContainer = primaryContainerDarkMediumContrast, + onPrimaryContainer = onPrimaryContainerDarkMediumContrast, + secondary = secondaryDarkMediumContrast, + onSecondary = onSecondaryDarkMediumContrast, + secondaryContainer = secondaryContainerDarkMediumContrast, + onSecondaryContainer = onSecondaryContainerDarkMediumContrast, + tertiary = tertiaryDarkMediumContrast, + onTertiary = onTertiaryDarkMediumContrast, + tertiaryContainer = tertiaryContainerDarkMediumContrast, + onTertiaryContainer = onTertiaryContainerDarkMediumContrast, + error = errorDarkMediumContrast, + onError = onErrorDarkMediumContrast, + errorContainer = errorContainerDarkMediumContrast, + onErrorContainer = onErrorContainerDarkMediumContrast, + background = backgroundDarkMediumContrast, + onBackground = onBackgroundDarkMediumContrast, + surface = surfaceDarkMediumContrast, + onSurface = onSurfaceDarkMediumContrast, + surfaceVariant = surfaceVariantDarkMediumContrast, + onSurfaceVariant = onSurfaceVariantDarkMediumContrast, + outline = outlineDarkMediumContrast, + outlineVariant = outlineVariantDarkMediumContrast, + scrim = scrimDarkMediumContrast, + inverseSurface = inverseSurfaceDarkMediumContrast, + inverseOnSurface = inverseOnSurfaceDarkMediumContrast, + inversePrimary = inversePrimaryDarkMediumContrast, + surfaceDim = surfaceDimDarkMediumContrast, + surfaceBright = surfaceBrightDarkMediumContrast, + surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast, + surfaceContainerLow = surfaceContainerLowDarkMediumContrast, + surfaceContainer = surfaceContainerDarkMediumContrast, + surfaceContainerHigh = surfaceContainerHighDarkMediumContrast, + surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast, +) - surface = ProcessingColors.darkerGray, - onSurface = ProcessingColors.lightGray, +private val highContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkHighContrast, + onPrimary = onPrimaryDarkHighContrast, + primaryContainer = primaryContainerDarkHighContrast, + onPrimaryContainer = onPrimaryContainerDarkHighContrast, + secondary = secondaryDarkHighContrast, + onSecondary = onSecondaryDarkHighContrast, + secondaryContainer = secondaryContainerDarkHighContrast, + onSecondaryContainer = onSecondaryContainerDarkHighContrast, + tertiary = tertiaryDarkHighContrast, + onTertiary = onTertiaryDarkHighContrast, + tertiaryContainer = tertiaryContainerDarkHighContrast, + onTertiaryContainer = onTertiaryContainerDarkHighContrast, + error = errorDarkHighContrast, + onError = onErrorDarkHighContrast, + errorContainer = errorContainerDarkHighContrast, + onErrorContainer = onErrorContainerDarkHighContrast, + background = backgroundDarkHighContrast, + onBackground = onBackgroundDarkHighContrast, + surface = surfaceDarkHighContrast, + onSurface = onSurfaceDarkHighContrast, + surfaceVariant = surfaceVariantDarkHighContrast, + onSurfaceVariant = onSurfaceVariantDarkHighContrast, + outline = outlineDarkHighContrast, + outlineVariant = outlineVariantDarkHighContrast, + scrim = scrimDarkHighContrast, + inverseSurface = inverseSurfaceDarkHighContrast, + inverseOnSurface = inverseOnSurfaceDarkHighContrast, + inversePrimary = inversePrimaryDarkHighContrast, + surfaceDim = surfaceDimDarkHighContrast, + surfaceBright = surfaceBrightDarkHighContrast, + surfaceContainerLowest = surfaceContainerLowestDarkHighContrast, + surfaceContainerLow = surfaceContainerLowDarkHighContrast, + surfaceContainer = surfaceContainerDarkHighContrast, + surfaceContainerHigh = surfaceContainerHighDarkHighContrast, + surfaceContainerHighest = surfaceContainerHighestDarkHighContrast, +) - error = ProcessingColors.error, - onError = ProcessingColors.white, -) \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 581cf30419..04b5df844e 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -67,7 +67,7 @@ fun PDETheme( PreferencesProvider { LocaleProvider { MaterialTheme( - colorScheme = if(darkTheme) PDE3DarkColor else PDE3LightColor, + colorScheme = if(darkTheme) darkScheme else lightScheme, typography = PDE3Typography ){ Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { From 1b564d876d97704e8b3c356d11621c1f6e2888e4 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 20 Oct 2025 13:33:52 +0200 Subject: [PATCH 34/50] Revert "Update color schemes to Material Theme Builder output" This reverts commit 27efb0be969c5390464ad37248153990d169fd65. --- app/src/processing/app/ui/theme/Colors.kt | 487 ++-------------------- app/src/processing/app/ui/theme/Theme.kt | 2 +- 2 files changed, 45 insertions(+), 444 deletions(-) diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index 5d74df0ed9..0a619b406f 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -82,453 +82,54 @@ val PDEDarkColors = Colors( isLight = false, ) -/* - * Generated using Material Theme Builder - * https://material-foundation.github.io/material-theme-builder/ -*/ - -val primaryLight = Color(0xFF003B98) -val onPrimaryLight = Color(0xFFFFFFFF) -val primaryContainerLight = Color(0xFF0251C8) -val onPrimaryContainerLight = Color(0xFFC1D0FF) -val secondaryLight = Color(0xFF00188B) -val onSecondaryLight = Color(0xFFFFFFFF) -val secondaryContainerLight = Color(0xFF1E32AA) -val onSecondaryContainerLight = Color(0xFF9DA9FF) -val tertiaryLight = Color(0xFFB8004A) -val onTertiaryLight = Color(0xFFFFFFFF) -val tertiaryContainerLight = Color(0xFFDE2360) -val onTertiaryContainerLight = Color(0xFFFFFBFF) -val errorLight = Color(0xFFB6212A) -val onErrorLight = Color(0xFFFFFFFF) -val errorContainerLight = Color(0xFFFF5757) -val onErrorContainerLight = Color(0xFF5E000A) -val backgroundLight = Color(0xFFFAF8FF) -val onBackgroundLight = Color(0xFF191B23) -val surfaceLight = Color(0xFFFCF8F8) -val onSurfaceLight = Color(0xFF1C1B1B) -val surfaceVariantLight = Color(0xFFE0E3E3) -val onSurfaceVariantLight = Color(0xFF444748) -val outlineLight = Color(0xFF747878) -val outlineVariantLight = Color(0xFFC4C7C7) -val scrimLight = Color(0xFF000000) -val inverseSurfaceLight = Color(0xFF313030) -val inverseOnSurfaceLight = Color(0xFFF4F0EF) -val inversePrimaryLight = Color(0xFFB2C5FF) -val surfaceDimLight = Color(0xFFDDD9D9) -val surfaceBrightLight = Color(0xFFFCF8F8) -val surfaceContainerLowestLight = Color(0xFFFFFFFF) -val surfaceContainerLowLight = Color(0xFFF7F3F2) -val surfaceContainerLight = Color(0xFFF1EDEC) -val surfaceContainerHighLight = Color(0xFFEBE7E7) -val surfaceContainerHighestLight = Color(0xFFE5E2E1) - -val primaryLightMediumContrast = Color(0xFF00317F) -val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) -val primaryContainerLightMediumContrast = Color(0xFF0251C8) -val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) -val secondaryLightMediumContrast = Color(0xFF00188B) -val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) -val secondaryContainerLightMediumContrast = Color(0xFF1E32AA) -val onSecondaryContainerLightMediumContrast = Color(0xFFD2D6FF) -val tertiaryLightMediumContrast = Color(0xFF71002A) -val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) -val tertiaryContainerLightMediumContrast = Color(0xFFD31859) -val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) -val errorLightMediumContrast = Color(0xFF73000F) -val onErrorLightMediumContrast = Color(0xFFFFFFFF) -val errorContainerLightMediumContrast = Color(0xFFCB3136) -val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) -val backgroundLightMediumContrast = Color(0xFFFAF8FF) -val onBackgroundLightMediumContrast = Color(0xFF191B23) -val surfaceLightMediumContrast = Color(0xFFFCF8F8) -val onSurfaceLightMediumContrast = Color(0xFF111111) -val surfaceVariantLightMediumContrast = Color(0xFFE0E3E3) -val onSurfaceVariantLightMediumContrast = Color(0xFF333737) -val outlineLightMediumContrast = Color(0xFF4F5354) -val outlineVariantLightMediumContrast = Color(0xFF6A6E6E) -val scrimLightMediumContrast = Color(0xFF000000) -val inverseSurfaceLightMediumContrast = Color(0xFF313030) -val inverseOnSurfaceLightMediumContrast = Color(0xFFF4F0EF) -val inversePrimaryLightMediumContrast = Color(0xFFB2C5FF) -val surfaceDimLightMediumContrast = Color(0xFFC9C6C5) -val surfaceBrightLightMediumContrast = Color(0xFFFCF8F8) -val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) -val surfaceContainerLowLightMediumContrast = Color(0xFFF7F3F2) -val surfaceContainerLightMediumContrast = Color(0xFFEBE7E7) -val surfaceContainerHighLightMediumContrast = Color(0xFFE0DCDB) -val surfaceContainerHighestLightMediumContrast = Color(0xFFD4D1D0) - -val primaryLightHighContrast = Color(0xFF00276A) -val onPrimaryLightHighContrast = Color(0xFFFFFFFF) -val primaryContainerLightHighContrast = Color(0xFF0042A7) -val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) -val secondaryLightHighContrast = Color(0xFF00188B) -val onSecondaryLightHighContrast = Color(0xFFFFFFFF) -val secondaryContainerLightHighContrast = Color(0xFF1E32AA) -val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) -val tertiaryLightHighContrast = Color(0xFF5E0022) -val onTertiaryLightHighContrast = Color(0xFFFFFFFF) -val tertiaryContainerLightHighContrast = Color(0xFF95003A) -val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) -val errorLightHighContrast = Color(0xFF60000B) -val onErrorLightHighContrast = Color(0xFFFFFFFF) -val errorContainerLightHighContrast = Color(0xFF970117) -val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) -val backgroundLightHighContrast = Color(0xFFFAF8FF) -val onBackgroundLightHighContrast = Color(0xFF191B23) -val surfaceLightHighContrast = Color(0xFFFCF8F8) -val onSurfaceLightHighContrast = Color(0xFF000000) -val surfaceVariantLightHighContrast = Color(0xFFE0E3E3) -val onSurfaceVariantLightHighContrast = Color(0xFF000000) -val outlineLightHighContrast = Color(0xFF292D2D) -val outlineVariantLightHighContrast = Color(0xFF464A4A) -val scrimLightHighContrast = Color(0xFF000000) -val inverseSurfaceLightHighContrast = Color(0xFF313030) -val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) -val inversePrimaryLightHighContrast = Color(0xFFB2C5FF) -val surfaceDimLightHighContrast = Color(0xFFBBB8B7) -val surfaceBrightLightHighContrast = Color(0xFFFCF8F8) -val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) -val surfaceContainerLowLightHighContrast = Color(0xFFF4F0EF) -val surfaceContainerLightHighContrast = Color(0xFFE5E2E1) -val surfaceContainerHighLightHighContrast = Color(0xFFD7D4D3) -val surfaceContainerHighestLightHighContrast = Color(0xFFC9C6C5) - -val primaryDark = Color(0xFFB2C5FF) -val onPrimaryDark = Color(0xFF002B73) -val primaryContainerDark = Color(0xFF0251C8) -val onPrimaryContainerDark = Color(0xFFC1D0FF) -val secondaryDark = Color(0xFFBBC3FF) -val onSecondaryDark = Color(0xFF001B96) -val secondaryContainerDark = Color(0xFF1E32AA) -val onSecondaryContainerDark = Color(0xFF9DA9FF) -val tertiaryDark = Color(0xFFFFB2BE) -val onTertiaryDark = Color(0xFF660026) -val tertiaryContainerDark = Color(0xFFFF4E7D) -val onTertiaryContainerDark = Color(0xFF52001C) -val errorDark = Color(0xFFFFB3AF) -val onErrorDark = Color(0xFF68000D) -val errorContainerDark = Color(0xFFFF5757) -val onErrorContainerDark = Color(0xFF5E000A) -val backgroundDark = Color(0xFF11131A) -val onBackgroundDark = Color(0xFFE1E2EC) -val surfaceDark = Color(0xFF141313) -val onSurfaceDark = Color(0xFFE5E2E1) -val surfaceVariantDark = Color(0xFF444748) -val onSurfaceVariantDark = Color(0xFFC4C7C7) -val outlineDark = Color(0xFF8E9192) -val outlineVariantDark = Color(0xFF444748) -val scrimDark = Color(0xFF000000) -val inverseSurfaceDark = Color(0xFFE5E2E1) -val inverseOnSurfaceDark = Color(0xFF313030) -val inversePrimaryDark = Color(0xFF1256CD) -val surfaceDimDark = Color(0xFF141313) -val surfaceBrightDark = Color(0xFF3A3939) -val surfaceContainerLowestDark = Color(0xFF0E0E0E) -val surfaceContainerLowDark = Color(0xFF1C1B1B) -val surfaceContainerDark = Color(0xFF201F1F) -val surfaceContainerHighDark = Color(0xFF2A2A2A) -val surfaceContainerHighestDark = Color(0xFF353434) - -val primaryDarkMediumContrast = Color(0xFFD1DBFF) -val onPrimaryDarkMediumContrast = Color(0xFF00215D) -val primaryContainerDarkMediumContrast = Color(0xFF5C8CFF) -val onPrimaryContainerDarkMediumContrast = Color(0xFF000000) -val secondaryDarkMediumContrast = Color(0xFFD7DAFF) -val onSecondaryDarkMediumContrast = Color(0xFF00147A) -val secondaryContainerDarkMediumContrast = Color(0xFF7587FE) -val onSecondaryContainerDarkMediumContrast = Color(0xFF000000) -val tertiaryDarkMediumContrast = Color(0xFFFFD1D7) -val onTertiaryDarkMediumContrast = Color(0xFF52001D) -val tertiaryContainerDarkMediumContrast = Color(0xFFFF4E7D) -val onTertiaryContainerDarkMediumContrast = Color(0xFF000000) -val errorDarkMediumContrast = Color(0xFFFFD2CE) -val onErrorDarkMediumContrast = Color(0xFF540008) -val errorContainerDarkMediumContrast = Color(0xFFFF5757) -val onErrorContainerDarkMediumContrast = Color(0xFF000000) -val backgroundDarkMediumContrast = Color(0xFF11131A) -val onBackgroundDarkMediumContrast = Color(0xFFE1E2EC) -val surfaceDarkMediumContrast = Color(0xFF141313) -val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF) -val surfaceVariantDarkMediumContrast = Color(0xFF444748) -val onSurfaceVariantDarkMediumContrast = Color(0xFFDADDDD) -val outlineDarkMediumContrast = Color(0xFFAFB2B3) -val outlineVariantDarkMediumContrast = Color(0xFF8D9191) -val scrimDarkMediumContrast = Color(0xFF000000) -val inverseSurfaceDarkMediumContrast = Color(0xFFE5E2E1) -val inverseOnSurfaceDarkMediumContrast = Color(0xFF2A2A2A) -val inversePrimaryDarkMediumContrast = Color(0xFF0041A4) -val surfaceDimDarkMediumContrast = Color(0xFF141313) -val surfaceBrightDarkMediumContrast = Color(0xFF454444) -val surfaceContainerLowestDarkMediumContrast = Color(0xFF070707) -val surfaceContainerLowDarkMediumContrast = Color(0xFF1E1D1D) -val surfaceContainerDarkMediumContrast = Color(0xFF282827) -val surfaceContainerHighDarkMediumContrast = Color(0xFF333232) -val surfaceContainerHighestDarkMediumContrast = Color(0xFF3E3D3D) - -val primaryDarkHighContrast = Color(0xFFEDEFFF) -val onPrimaryDarkHighContrast = Color(0xFF000000) -val primaryContainerDarkHighContrast = Color(0xFFACC1FF) -val onPrimaryContainerDarkHighContrast = Color(0xFF000926) -val secondaryDarkHighContrast = Color(0xFFEFEEFF) -val onSecondaryDarkHighContrast = Color(0xFF000000) -val secondaryContainerDarkHighContrast = Color(0xFFB6BEFF) -val onSecondaryContainerDarkHighContrast = Color(0xFF000434) -val tertiaryDarkHighContrast = Color(0xFFFFEBED) -val onTertiaryDarkHighContrast = Color(0xFF000000) -val tertiaryContainerDarkHighContrast = Color(0xFFFFACB9) -val onTertiaryContainerDarkHighContrast = Color(0xFF210007) -val errorDarkHighContrast = Color(0xFFFFECEA) -val onErrorDarkHighContrast = Color(0xFF000000) -val errorContainerDarkHighContrast = Color(0xFFFFAEA9) -val onErrorContainerDarkHighContrast = Color(0xFF220001) -val backgroundDarkHighContrast = Color(0xFF11131A) -val onBackgroundDarkHighContrast = Color(0xFFE1E2EC) -val surfaceDarkHighContrast = Color(0xFF141313) -val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) -val surfaceVariantDarkHighContrast = Color(0xFF444748) -val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF) -val outlineDarkHighContrast = Color(0xFFEEF0F1) -val outlineVariantDarkHighContrast = Color(0xFFC0C3C4) -val scrimDarkHighContrast = Color(0xFF000000) -val inverseSurfaceDarkHighContrast = Color(0xFFE5E2E1) -val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) -val inversePrimaryDarkHighContrast = Color(0xFF0041A4) -val surfaceDimDarkHighContrast = Color(0xFF141313) -val surfaceBrightDarkHighContrast = Color(0xFF51504F) -val surfaceContainerLowestDarkHighContrast = Color(0xFF000000) -val surfaceContainerLowDarkHighContrast = Color(0xFF201F1F) -val surfaceContainerDarkHighContrast = Color(0xFF313030) -val surfaceContainerHighDarkHighContrast = Color(0xFF3C3B3B) -val surfaceContainerHighestDarkHighContrast = Color(0xFF484646) - - -val lightScheme = lightColorScheme( - primary = primaryLight, - onPrimary = onPrimaryLight, - primaryContainer = primaryContainerLight, - onPrimaryContainer = onPrimaryContainerLight, - secondary = secondaryLight, - onSecondary = onSecondaryLight, - secondaryContainer = secondaryContainerLight, - onSecondaryContainer = onSecondaryContainerLight, - tertiary = tertiaryLight, - onTertiary = onTertiaryLight, - tertiaryContainer = tertiaryContainerLight, - onTertiaryContainer = onTertiaryContainerLight, - error = errorLight, - onError = onErrorLight, - errorContainer = errorContainerLight, - onErrorContainer = onErrorContainerLight, - background = backgroundLight, - onBackground = onBackgroundLight, - surface = surfaceLight, - onSurface = onSurfaceLight, - surfaceVariant = surfaceVariantLight, - onSurfaceVariant = onSurfaceVariantLight, - outline = outlineLight, - outlineVariant = outlineVariantLight, - scrim = scrimLight, - inverseSurface = inverseSurfaceLight, - inverseOnSurface = inverseOnSurfaceLight, - inversePrimary = inversePrimaryLight, - surfaceDim = surfaceDimLight, - surfaceBright = surfaceBrightLight, - surfaceContainerLowest = surfaceContainerLowestLight, - surfaceContainerLow = surfaceContainerLowLight, - surfaceContainer = surfaceContainerLight, - surfaceContainerHigh = surfaceContainerHighLight, - surfaceContainerHighest = surfaceContainerHighestLight, -) +val PDE3LightColor = lightColorScheme( + primary = ProcessingColors.blue, + onPrimary = ProcessingColors.white, -val darkScheme = darkColorScheme( - primary = primaryDark, - onPrimary = onPrimaryDark, - primaryContainer = primaryContainerDark, - onPrimaryContainer = onPrimaryContainerDark, - secondary = secondaryDark, - onSecondary = onSecondaryDark, - secondaryContainer = secondaryContainerDark, - onSecondaryContainer = onSecondaryContainerDark, - tertiary = tertiaryDark, - onTertiary = onTertiaryDark, - tertiaryContainer = tertiaryContainerDark, - onTertiaryContainer = onTertiaryContainerDark, - error = errorDark, - onError = onErrorDark, - errorContainer = errorContainerDark, - onErrorContainer = onErrorContainerDark, - background = backgroundDark, - onBackground = onBackgroundDark, - surface = surfaceDark, - onSurface = onSurfaceDark, - surfaceVariant = surfaceVariantDark, - onSurfaceVariant = onSurfaceVariantDark, - outline = outlineDark, - outlineVariant = outlineVariantDark, - scrim = scrimDark, - inverseSurface = inverseSurfaceDark, - inverseOnSurface = inverseOnSurfaceDark, - inversePrimary = inversePrimaryDark, - surfaceDim = surfaceDimDark, - surfaceBright = surfaceBrightDark, - surfaceContainerLowest = surfaceContainerLowestDark, - surfaceContainerLow = surfaceContainerLowDark, - surfaceContainer = surfaceContainerDark, - surfaceContainerHigh = surfaceContainerHighDark, - surfaceContainerHighest = surfaceContainerHighestDark, -) + primaryContainer = ProcessingColors.downloadBackgroundActive, + onPrimaryContainer = ProcessingColors.darkBlue, -private val mediumContrastLightColorScheme = lightColorScheme( - primary = primaryLightMediumContrast, - onPrimary = onPrimaryLightMediumContrast, - primaryContainer = primaryContainerLightMediumContrast, - onPrimaryContainer = onPrimaryContainerLightMediumContrast, - secondary = secondaryLightMediumContrast, - onSecondary = onSecondaryLightMediumContrast, - secondaryContainer = secondaryContainerLightMediumContrast, - onSecondaryContainer = onSecondaryContainerLightMediumContrast, - tertiary = tertiaryLightMediumContrast, - onTertiary = onTertiaryLightMediumContrast, - tertiaryContainer = tertiaryContainerLightMediumContrast, - onTertiaryContainer = onTertiaryContainerLightMediumContrast, - error = errorLightMediumContrast, - onError = onErrorLightMediumContrast, - errorContainer = errorContainerLightMediumContrast, - onErrorContainer = onErrorContainerLightMediumContrast, - background = backgroundLightMediumContrast, - onBackground = onBackgroundLightMediumContrast, - surface = surfaceLightMediumContrast, - onSurface = onSurfaceLightMediumContrast, - surfaceVariant = surfaceVariantLightMediumContrast, - onSurfaceVariant = onSurfaceVariantLightMediumContrast, - outline = outlineLightMediumContrast, - outlineVariant = outlineVariantLightMediumContrast, - scrim = scrimLightMediumContrast, - inverseSurface = inverseSurfaceLightMediumContrast, - inverseOnSurface = inverseOnSurfaceLightMediumContrast, - inversePrimary = inversePrimaryLightMediumContrast, - surfaceDim = surfaceDimLightMediumContrast, - surfaceBright = surfaceBrightLightMediumContrast, - surfaceContainerLowest = surfaceContainerLowestLightMediumContrast, - surfaceContainerLow = surfaceContainerLowLightMediumContrast, - surfaceContainer = surfaceContainerLightMediumContrast, - surfaceContainerHigh = surfaceContainerHighLightMediumContrast, - surfaceContainerHighest = surfaceContainerHighestLightMediumContrast, -) + secondary = ProcessingColors.deepBlue, + onSecondary = ProcessingColors.white, -private val highContrastLightColorScheme = lightColorScheme( - primary = primaryLightHighContrast, - onPrimary = onPrimaryLightHighContrast, - primaryContainer = primaryContainerLightHighContrast, - onPrimaryContainer = onPrimaryContainerLightHighContrast, - secondary = secondaryLightHighContrast, - onSecondary = onSecondaryLightHighContrast, - secondaryContainer = secondaryContainerLightHighContrast, - onSecondaryContainer = onSecondaryContainerLightHighContrast, - tertiary = tertiaryLightHighContrast, - onTertiary = onTertiaryLightHighContrast, - tertiaryContainer = tertiaryContainerLightHighContrast, - onTertiaryContainer = onTertiaryContainerLightHighContrast, - error = errorLightHighContrast, - onError = onErrorLightHighContrast, - errorContainer = errorContainerLightHighContrast, - onErrorContainer = onErrorContainerLightHighContrast, - background = backgroundLightHighContrast, - onBackground = onBackgroundLightHighContrast, - surface = surfaceLightHighContrast, - onSurface = onSurfaceLightHighContrast, - surfaceVariant = surfaceVariantLightHighContrast, - onSurfaceVariant = onSurfaceVariantLightHighContrast, - outline = outlineLightHighContrast, - outlineVariant = outlineVariantLightHighContrast, - scrim = scrimLightHighContrast, - inverseSurface = inverseSurfaceLightHighContrast, - inverseOnSurface = inverseOnSurfaceLightHighContrast, - inversePrimary = inversePrimaryLightHighContrast, - surfaceDim = surfaceDimLightHighContrast, - surfaceBright = surfaceBrightLightHighContrast, - surfaceContainerLowest = surfaceContainerLowestLightHighContrast, - surfaceContainerLow = surfaceContainerLowLightHighContrast, - surfaceContainer = surfaceContainerLightHighContrast, - surfaceContainerHigh = surfaceContainerHighLightHighContrast, - surfaceContainerHighest = surfaceContainerHighestLightHighContrast, -) + secondaryContainer = ProcessingColors.downloadInactive, + onSecondaryContainer = ProcessingColors.white, -private val mediumContrastDarkColorScheme = darkColorScheme( - primary = primaryDarkMediumContrast, - onPrimary = onPrimaryDarkMediumContrast, - primaryContainer = primaryContainerDarkMediumContrast, - onPrimaryContainer = onPrimaryContainerDarkMediumContrast, - secondary = secondaryDarkMediumContrast, - onSecondary = onSecondaryDarkMediumContrast, - secondaryContainer = secondaryContainerDarkMediumContrast, - onSecondaryContainer = onSecondaryContainerDarkMediumContrast, - tertiary = tertiaryDarkMediumContrast, - onTertiary = onTertiaryDarkMediumContrast, - tertiaryContainer = tertiaryContainerDarkMediumContrast, - onTertiaryContainer = onTertiaryContainerDarkMediumContrast, - error = errorDarkMediumContrast, - onError = onErrorDarkMediumContrast, - errorContainer = errorContainerDarkMediumContrast, - onErrorContainer = onErrorContainerDarkMediumContrast, - background = backgroundDarkMediumContrast, - onBackground = onBackgroundDarkMediumContrast, - surface = surfaceDarkMediumContrast, - onSurface = onSurfaceDarkMediumContrast, - surfaceVariant = surfaceVariantDarkMediumContrast, - onSurfaceVariant = onSurfaceVariantDarkMediumContrast, - outline = outlineDarkMediumContrast, - outlineVariant = outlineVariantDarkMediumContrast, - scrim = scrimDarkMediumContrast, - inverseSurface = inverseSurfaceDarkMediumContrast, - inverseOnSurface = inverseOnSurfaceDarkMediumContrast, - inversePrimary = inversePrimaryDarkMediumContrast, - surfaceDim = surfaceDimDarkMediumContrast, - surfaceBright = surfaceBrightDarkMediumContrast, - surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast, - surfaceContainerLow = surfaceContainerLowDarkMediumContrast, - surfaceContainer = surfaceContainerDarkMediumContrast, - surfaceContainerHigh = surfaceContainerHighDarkMediumContrast, - surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast, -) + tertiary = ProcessingColors.p5Mid, + onTertiary = ProcessingColors.white, + + tertiaryContainer = ProcessingColors.p5Light, + onTertiaryContainer = ProcessingColors.p5Dark, + + background = ProcessingColors.white, + onBackground = ProcessingColors.darkBlue, + + surface = ProcessingColors.lightGray, + onSurface = ProcessingColors.darkerGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, -private val highContrastDarkColorScheme = darkColorScheme( - primary = primaryDarkHighContrast, - onPrimary = onPrimaryDarkHighContrast, - primaryContainer = primaryContainerDarkHighContrast, - onPrimaryContainer = onPrimaryContainerDarkHighContrast, - secondary = secondaryDarkHighContrast, - onSecondary = onSecondaryDarkHighContrast, - secondaryContainer = secondaryContainerDarkHighContrast, - onSecondaryContainer = onSecondaryContainerDarkHighContrast, - tertiary = tertiaryDarkHighContrast, - onTertiary = onTertiaryDarkHighContrast, - tertiaryContainer = tertiaryContainerDarkHighContrast, - onTertiaryContainer = onTertiaryContainerDarkHighContrast, - error = errorDarkHighContrast, - onError = onErrorDarkHighContrast, - errorContainer = errorContainerDarkHighContrast, - onErrorContainer = onErrorContainerDarkHighContrast, - background = backgroundDarkHighContrast, - onBackground = onBackgroundDarkHighContrast, - surface = surfaceDarkHighContrast, - onSurface = onSurfaceDarkHighContrast, - surfaceVariant = surfaceVariantDarkHighContrast, - onSurfaceVariant = onSurfaceVariantDarkHighContrast, - outline = outlineDarkHighContrast, - outlineVariant = outlineVariantDarkHighContrast, - scrim = scrimDarkHighContrast, - inverseSurface = inverseSurfaceDarkHighContrast, - inverseOnSurface = inverseOnSurfaceDarkHighContrast, - inversePrimary = inversePrimaryDarkHighContrast, - surfaceDim = surfaceDimDarkHighContrast, - surfaceBright = surfaceBrightDarkHighContrast, - surfaceContainerLowest = surfaceContainerLowestDarkHighContrast, - surfaceContainerLow = surfaceContainerLowDarkHighContrast, - surfaceContainer = surfaceContainerDarkHighContrast, - surfaceContainerHigh = surfaceContainerHighDarkHighContrast, - surfaceContainerHighest = surfaceContainerHighestDarkHighContrast, + errorContainer = ProcessingColors.errorContainer, + onErrorContainer = ProcessingColors.white ) +val PDE3DarkColor = darkColorScheme( + primary = ProcessingColors.deepBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.lightBlue, + onSecondary = ProcessingColors.white, + + tertiary = ProcessingColors.blue, + onTertiary = ProcessingColors.white, + + background = ProcessingColors.veryDarkGray, + onBackground = ProcessingColors.white, + + surface = ProcessingColors.darkerGray, + onSurface = ProcessingColors.lightGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, +) \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 04b5df844e..581cf30419 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -67,7 +67,7 @@ fun PDETheme( PreferencesProvider { LocaleProvider { MaterialTheme( - colorScheme = if(darkTheme) darkScheme else lightScheme, + colorScheme = if(darkTheme) PDE3DarkColor else PDE3LightColor, typography = PDE3Typography ){ Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { From a996ec305ced3352d0f777a932f97afe9500d6b8 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 10:10:09 +0200 Subject: [PATCH 35/50] Add RangeSlider to theme component previews Introduced Material3 RangeSlider to the theme preview UI alongside the existing Slider. Also provided a custom Density in ProcessingTheme for improved UI scaling. --- app/src/processing/app/ui/theme/ProcessingTheme.kt | 4 +++- app/src/processing/app/ui/theme/Theme.kt | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/ui/theme/ProcessingTheme.kt b/app/src/processing/app/ui/theme/ProcessingTheme.kt index 4fab37a371..374552bb64 100644 --- a/app/src/processing/app/ui/theme/ProcessingTheme.kt +++ b/app/src/processing/app/ui/theme/ProcessingTheme.kt @@ -54,7 +54,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.state.ToggleableState +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window @@ -100,7 +102,7 @@ fun ProcessingTheme( PreferencesProvider { val preferences = LocalPreferences.current val theme = ProcessingTheme(preferences.getProperty("theme")) - CompositionLocalProvider(LocalProcessingTheme provides theme) { + CompositionLocalProvider(LocalProcessingTheme provides theme,LocalDensity provides Density(1.25f, 1.25f),) { LocaleProvider { MaterialTheme( colors = if (darkTheme) PDEDarkColors else PDELightColors, diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 581cf30419..2c81f0628e 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -34,6 +34,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton +import androidx.compose.material3.RangeSlider import androidx.compose.material3.Slider import androidx.compose.material3.Switch import androidx.compose.material3.TextButton @@ -251,8 +252,12 @@ fun main() { Switch(!state, enabled = false, onCheckedChange = { state = it }) } ComponentPreview("Slider") { - var state by remember { mutableStateOf(0.5f) } - Slider(state, onValueChange = { state = it }) + Column{ + var state by remember { mutableStateOf(0.5f) } + Slider(state, onValueChange = { state = it }) + var rangeState by remember { mutableStateOf(0.25f..0.75f) } + RangeSlider(rangeState, onValueChange = { rangeState = it }) + } } ComponentPreview("Badge") { From 213288a45956ef2bf87a3b3bb03f3a27234c5d1a Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 10:27:54 +0200 Subject: [PATCH 36/50] Refactor theme naming and update usage in UI Renamed theme-related variables to clarify Material2 vs Material3 usage (e.g., PDE2LightColors, PDELightColor). Updated ProcessingTheme and PDETheme to use new names and improved documentation. Deprecated old typography and shapes variables in favor of new ones. --- app/src/processing/app/ui/theme/Colors.kt | 9 ++--- .../app/ui/theme/ProcessingTheme.kt | 7 ++-- app/src/processing/app/ui/theme/Shapes.kt | 2 +- app/src/processing/app/ui/theme/Theme.kt | 37 ++++++++++++++++--- app/src/processing/app/ui/theme/Typography.kt | 5 ++- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt index 0a619b406f..61c6d6b55f 100644 --- a/app/src/processing/app/ui/theme/Colors.kt +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -1,7 +1,6 @@ package processing.app.ui.theme import androidx.compose.material.Colors -import androidx.compose.material3.ColorScheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.ui.graphics.Color @@ -39,7 +38,7 @@ class ProcessingColors{ } @Deprecated("Use PDE3LightColor instead") -val PDELightColors = Colors( +val PDE2LightColors = Colors( primary = ProcessingColors.blue, primaryVariant = ProcessingColors.lightBlue, onPrimary = ProcessingColors.white, @@ -61,7 +60,7 @@ val PDELightColors = Colors( ) @Deprecated("Use PDE3DarkColor instead") -val PDEDarkColors = Colors( +val PDE2DarkColors = Colors( primary = ProcessingColors.deepBlue, primaryVariant = ProcessingColors.darkBlue, onPrimary = ProcessingColors.white, @@ -82,7 +81,7 @@ val PDEDarkColors = Colors( isLight = false, ) -val PDE3LightColor = lightColorScheme( +val PDELightColor = lightColorScheme( primary = ProcessingColors.blue, onPrimary = ProcessingColors.white, @@ -114,7 +113,7 @@ val PDE3LightColor = lightColorScheme( onErrorContainer = ProcessingColors.white ) -val PDE3DarkColor = darkColorScheme( +val PDEDarkColor = darkColorScheme( primary = ProcessingColors.deepBlue, onPrimary = ProcessingColors.white, diff --git a/app/src/processing/app/ui/theme/ProcessingTheme.kt b/app/src/processing/app/ui/theme/ProcessingTheme.kt index 374552bb64..cfcaa6cacf 100644 --- a/app/src/processing/app/ui/theme/ProcessingTheme.kt +++ b/app/src/processing/app/ui/theme/ProcessingTheme.kt @@ -27,7 +27,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FilterChip import androidx.compose.material.Icon import androidx.compose.material.IconButton -import androidx.compose.material.IconToggleButton import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.LocalRippleConfiguration import androidx.compose.material.MaterialTheme @@ -105,9 +104,9 @@ fun ProcessingTheme( CompositionLocalProvider(LocalProcessingTheme provides theme,LocalDensity provides Density(1.25f, 1.25f),) { LocaleProvider { MaterialTheme( - colors = if (darkTheme) PDEDarkColors else PDELightColors, - typography = PDETypography, - shapes = PDEShapes + colors = if (darkTheme) PDE2DarkColors else PDE2LightColors, + typography = PDE2Typography, + shapes = PDE2Shapes ) { CompositionLocalProvider( LocalRippleConfiguration provides RippleConfiguration( diff --git a/app/src/processing/app/ui/theme/Shapes.kt b/app/src/processing/app/ui/theme/Shapes.kt index 581b235593..95fefb345e 100644 --- a/app/src/processing/app/ui/theme/Shapes.kt +++ b/app/src/processing/app/ui/theme/Shapes.kt @@ -4,7 +4,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Shapes import androidx.compose.ui.unit.dp -val PDEShapes = Shapes( +val PDE2Shapes = Shapes( small = RoundedCornerShape(30.dp), medium = RoundedCornerShape(6.dp), large = RoundedCornerShape(6.dp) diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 2c81f0628e..7cc70455f0 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -1,7 +1,6 @@ package processing.app.ui.theme import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -48,7 +47,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.Density @@ -60,6 +58,30 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import processing.app.PreferencesProvider +/** + * Processing Theme for Jetpack Compose Desktop + * Based on Material3 + * + * Makes Material3 components follow Processing color scheme and typography + * We experimented with using the material3 theme builder, but it made it look too Android-y + * So we defined our own color scheme and typography based on Processing design guidelines + * + * This composable also provides Preferences and Locale context to all child composables + * + * Also, important: sets a default density of 1.25 for better scaling on desktop screens, [LocalDensity] + * + * Usage: + * ``` + * PDETheme { + * val pref = LocalPreferences.current + * val locale = LocalLocale.current + * ... + * // Your composables here + * } + * ``` + * + * @param darkTheme Whether to use dark theme or light theme. Defaults to system setting. + */ @Composable fun PDETheme( darkTheme: Boolean = isSystemInDarkTheme(), @@ -68,8 +90,8 @@ fun PDETheme( PreferencesProvider { LocaleProvider { MaterialTheme( - colorScheme = if(darkTheme) PDE3DarkColor else PDE3LightColor, - typography = PDE3Typography + colorScheme = if(darkTheme) PDEDarkColor else PDELightColor, + typography = PDETypography ){ Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { CompositionLocalProvider( @@ -83,6 +105,11 @@ fun PDETheme( } } +/** + * Simple app to preview the Processing Theme components + * Includes buttons, text fields, checkboxes, sliders, etc. + * Run by executing the main() function by clicking the green arrow next to it in intelliJ IDEA + */ fun main() { application { val windowState = rememberWindowState( @@ -91,7 +118,7 @@ fun main() { ) var darkTheme by remember { mutableStateOf(false) } Window(onCloseRequest = ::exitApplication, state = windowState, title = "Processing Theme") { - PDETheme(darkTheme) { + PDETheme(darkTheme = darkTheme) { Column(modifier = Modifier.padding(16.dp)) { Text("Processing Theme Components", style = MaterialTheme.typography.titleLarge) Card { diff --git a/app/src/processing/app/ui/theme/Typography.kt b/app/src/processing/app/ui/theme/Typography.kt index 1d1c139f1c..6650ac7167 100644 --- a/app/src/processing/app/ui/theme/Typography.kt +++ b/app/src/processing/app/ui/theme/Typography.kt @@ -43,7 +43,8 @@ val spaceGroteskFont = FontFamily( ) ) -val PDETypography = Typography( +@Deprecated("Use PDE3Typography instead") +val PDE2Typography = Typography( defaultFontFamily = spaceGroteskFont, h1 = TextStyle( fontWeight = FontWeight.Bold, @@ -107,7 +108,7 @@ val PDETypography = Typography( ) ) val base = androidx.compose.material3.Typography() -val PDE3Typography = androidx.compose.material3.Typography( +val PDETypography = androidx.compose.material3.Typography( displayLarge = base.displayLarge.copy(fontFamily = spaceGroteskFont), displayMedium = base.displayMedium.copy(fontFamily = spaceGroteskFont), displaySmall = base.displaySmall.copy(fontFamily = spaceGroteskFont), From ad1268fc3e4114056c4e476ae1ef4a8d4e735639 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 10:29:18 +0200 Subject: [PATCH 37/50] Add Material3-based Processing theme and typography Introduces Colors.kt with custom color schemes for light and dark themes using Material3. Refactors Theme.kt to use Material3 theming, adds a PDETheme composable, and provides a desktop preview app for theme components. Updates Typography.kt to use Space Grotesk font family and defines new typography styles for Material3. --- app/build.gradle.kts | 16 +- app/src/processing/app/ui/theme/Colors.kt | 134 ++++++ app/src/processing/app/ui/theme/Theme.kt | 399 +++++++++++++++--- app/src/processing/app/ui/theme/Typography.kt | 101 ++++- gradle/libs.versions.toml | 6 +- 5 files changed, 587 insertions(+), 69 deletions(-) create mode 100644 app/src/processing/app/ui/theme/Colors.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d3fcbd12d..6c8ac55f00 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ import org.gradle.internal.jvm.Jvm import org.gradle.internal.os.OperatingSystem import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download @@ -16,6 +17,7 @@ plugins{ alias(libs.plugins.compose.compiler) alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.serialization) alias(libs.plugins.download) } @@ -59,7 +61,7 @@ compose.desktop { ).map { "-D${it.first}=${it.second}" }.toTypedArray()) nativeDistributions{ - modules("jdk.jdi", "java.compiler", "jdk.accessibility", "java.management.rmi", "java.scripting", "jdk.httpserver") + modules("jdk.jdi", "java.compiler", "jdk.accessibility", "jdk.zipfs", "java.management.rmi", "java.scripting", "jdk.httpserver") targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "Processing" @@ -107,25 +109,29 @@ dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) + implementation(compose.materialIconsExtended) implementation(compose.desktop.currentOs) + implementation(libs.material3) implementation(libs.compottie) implementation(libs.kaml) implementation(libs.markdown) implementation(libs.markdownJVM) + implementation(libs.clikt) + implementation(libs.kotlinxSerializationJson) + + @OptIn(ExperimentalComposeLibrary::class) + testImplementation(compose.uiTest) testImplementation(kotlin("test")) testImplementation(libs.mockitoKotlin) testImplementation(libs.junitJupiter) testImplementation(libs.junitJupiterParams) - - implementation(libs.clikt) - implementation(libs.kotlinxSerializationJson) + } tasks.test { diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt new file mode 100644 index 0000000000..61c6d6b55f --- /dev/null +++ b/app/src/processing/app/ui/theme/Colors.kt @@ -0,0 +1,134 @@ +package processing.app.ui.theme + +import androidx.compose.material.Colors +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.ui.graphics.Color + +class ProcessingColors{ + companion object{ + val blue = Color(0xFF0251c8) + val lightBlue = Color(0xFF82AFFF) + + val deepBlue = Color(0xFF1e32aa) + val darkBlue = Color(0xFF0F195A) + + val white = Color(0xFFFFFFFF) + val lightGray = Color(0xFFF5F5F5) + val gray = Color(0xFFDBDBDB) + val darkGray = Color(0xFF898989) + val darkerGray = Color(0xFF727070) + val veryDarkGray = Color(0xFF1E1E1E) + val black = Color(0xFF0D0D0D) + + val error = Color(0xFFFF5757) + val errorContainer = Color(0xFFFFA6A6) + + val p5Light = Color(0xFFfd9db9) + val p5Mid = Color(0xFFff4077) + val p5Dark = Color(0xFFaf1f42) + + val foundationLight = Color(0xFFd4b2fe) + val foundationMid = Color(0xFF9c4bff) + val foundationDark = Color(0xFF5501a4) + + val downloadInactive = Color(0xFF8890B3) + val downloadBackgroundActive = Color(0x14508BFF) + } +} + +@Deprecated("Use PDE3LightColor instead") +val PDE2LightColors = Colors( + primary = ProcessingColors.blue, + primaryVariant = ProcessingColors.lightBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.deepBlue, + secondaryVariant = ProcessingColors.darkBlue, + onSecondary = ProcessingColors.white, + + background = ProcessingColors.white, + onBackground = ProcessingColors.darkBlue, + + surface = ProcessingColors.lightGray, + onSurface = ProcessingColors.darkerGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, + + isLight = true, +) + +@Deprecated("Use PDE3DarkColor instead") +val PDE2DarkColors = Colors( + primary = ProcessingColors.deepBlue, + primaryVariant = ProcessingColors.darkBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.lightBlue, + secondaryVariant = ProcessingColors.blue, + onSecondary = ProcessingColors.white, + + background = ProcessingColors.veryDarkGray, + onBackground = ProcessingColors.white, + + surface = ProcessingColors.darkerGray, + onSurface = ProcessingColors.lightGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, + + isLight = false, +) + +val PDELightColor = lightColorScheme( + primary = ProcessingColors.blue, + onPrimary = ProcessingColors.white, + + primaryContainer = ProcessingColors.downloadBackgroundActive, + onPrimaryContainer = ProcessingColors.darkBlue, + + secondary = ProcessingColors.deepBlue, + onSecondary = ProcessingColors.white, + + secondaryContainer = ProcessingColors.downloadInactive, + onSecondaryContainer = ProcessingColors.white, + + tertiary = ProcessingColors.p5Mid, + onTertiary = ProcessingColors.white, + + tertiaryContainer = ProcessingColors.p5Light, + onTertiaryContainer = ProcessingColors.p5Dark, + + background = ProcessingColors.white, + onBackground = ProcessingColors.darkBlue, + + surface = ProcessingColors.lightGray, + onSurface = ProcessingColors.darkerGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, + + errorContainer = ProcessingColors.errorContainer, + onErrorContainer = ProcessingColors.white +) + +val PDEDarkColor = darkColorScheme( + primary = ProcessingColors.deepBlue, + onPrimary = ProcessingColors.white, + + secondary = ProcessingColors.lightBlue, + onSecondary = ProcessingColors.white, + + tertiary = ProcessingColors.blue, + onTertiary = ProcessingColors.white, + + background = ProcessingColors.veryDarkGray, + onBackground = ProcessingColors.white, + + surface = ProcessingColors.darkerGray, + onSurface = ProcessingColors.lightGray, + + error = ProcessingColors.error, + onError = ProcessingColors.white, +) \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 735d8e5b2a..7cc70455f0 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -1,75 +1,364 @@ package processing.app.ui.theme +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.Colors -import androidx.compose.material.MaterialTheme +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.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Map +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.FilterChip +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RangeSlider +import androidx.compose.material3.Slider +import androidx.compose.material3.Switch +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TriStateCheckbox import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.graphics.Color -import processing.app.LocalPreferences +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.state.ToggleableState +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState import processing.app.PreferencesProvider -import java.io.InputStream -import java.util.Properties - - -class Theme(themeFile: String? = "") : Properties() { - init { - load(ClassLoader.getSystemResourceAsStream("theme.txt")) - load(ClassLoader.getSystemResourceAsStream(themeFile) ?: InputStream.nullInputStream()) - } - fun getColor(key: String): Color { - return Color(getProperty(key).toColorInt()) - } -} - -val LocalTheme = compositionLocalOf { error("No theme provided") } +/** + * Processing Theme for Jetpack Compose Desktop + * Based on Material3 + * + * Makes Material3 components follow Processing color scheme and typography + * We experimented with using the material3 theme builder, but it made it look too Android-y + * So we defined our own color scheme and typography based on Processing design guidelines + * + * This composable also provides Preferences and Locale context to all child composables + * + * Also, important: sets a default density of 1.25 for better scaling on desktop screens, [LocalDensity] + * + * Usage: + * ``` + * PDETheme { + * val pref = LocalPreferences.current + * val locale = LocalLocale.current + * ... + * // Your composables here + * } + * ``` + * + * @param darkTheme Whether to use dark theme or light theme. Defaults to system setting. + */ @Composable -fun ProcessingTheme( +fun PDETheme( darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable() () -> Unit -) { + content: @Composable () -> Unit +){ PreferencesProvider { - val preferences = LocalPreferences.current - val theme = Theme(preferences.getProperty("theme")) - val colors = Colors( - primary = theme.getColor("editor.gradient.top"), - primaryVariant = theme.getColor("toolbar.button.pressed.field"), - secondary = theme.getColor("editor.gradient.bottom"), - secondaryVariant = theme.getColor("editor.scrollbar.thumb.pressed.color"), - background = theme.getColor("editor.bgcolor"), - surface = theme.getColor("editor.bgcolor"), - error = theme.getColor("status.error.bgcolor"), - onPrimary = theme.getColor("toolbar.button.enabled.field"), - onSecondary = theme.getColor("toolbar.button.enabled.field"), - onBackground = theme.getColor("editor.fgcolor"), - onSurface = theme.getColor("editor.fgcolor"), - onError = theme.getColor("status.error.fgcolor"), - isLight = theme.getProperty("laf.mode").equals("light") + LocaleProvider { + MaterialTheme( + colorScheme = if(darkTheme) PDEDarkColor else PDELightColor, + typography = PDETypography + ){ + Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.onBackground, + LocalDensity provides Density(1.25f, 1.25f), + content = content + ) + } + } + } + } +} + +/** + * Simple app to preview the Processing Theme components + * Includes buttons, text fields, checkboxes, sliders, etc. + * Run by executing the main() function by clicking the green arrow next to it in intelliJ IDEA + */ +fun main() { + application { + val windowState = rememberWindowState( + size = DpSize(800.dp, 600.dp), + position = WindowPosition(Alignment.Center) ) + var darkTheme by remember { mutableStateOf(false) } + Window(onCloseRequest = ::exitApplication, state = windowState, title = "Processing Theme") { + PDETheme(darkTheme = darkTheme) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Processing Theme Components", style = MaterialTheme.typography.titleLarge) + Card { + Row { + Checkbox(darkTheme, onCheckedChange = { darkTheme = !darkTheme }) + Text( + "Dark Theme", + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + val scrollable = rememberScrollState() + Column( + modifier = Modifier + .verticalScroll(scrollable), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + ComponentPreview("Colors") { + Column { + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), + onClick = {}) { + Text("Primary", color = MaterialTheme.colorScheme.onPrimary) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary), + onClick = {}) { + Text("Secondary", color = MaterialTheme.colorScheme.onSecondary) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiary), + onClick = {}) { + Text("Tertiary", color = MaterialTheme.colorScheme.onTertiary) + } + } + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primaryContainer), + onClick = {}) { + Text("Primary Container", color = MaterialTheme.colorScheme.onPrimaryContainer) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer), + onClick = {}) { + Text("Secondary Container", color = MaterialTheme.colorScheme.onSecondaryContainer) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer), + onClick = {}) { + Text("Tertiary Container", color = MaterialTheme.colorScheme.onTertiaryContainer) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.errorContainer), + onClick = {}) { + Text("Error Container", color = MaterialTheme.colorScheme.onErrorContainer) + } + } + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background), + onClick = {}) { + Text("Background", color = MaterialTheme.colorScheme.onBackground) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surface), + onClick = {}) { + Text("Surface", color = MaterialTheme.colorScheme.onSurface) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), + onClick = {}) { + Text("Surface Variant", color = MaterialTheme.colorScheme.onSurfaceVariant) + } + Button( + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), + onClick = {}) { + Text("Error", color = MaterialTheme.colorScheme.onError) + } + } + } + } + ComponentPreview("Text & Fonts") { + Column { + Text("displayLarge", style = MaterialTheme.typography.displayLarge) + Text("displayMedium", style = MaterialTheme.typography.displayMedium) + Text("displaySmall", style = MaterialTheme.typography.displaySmall) + + Text("headlineLarge", style = MaterialTheme.typography.headlineLarge) + Text("headlineMedium", style = MaterialTheme.typography.headlineMedium) + Text("headlineSmall", style = MaterialTheme.typography.headlineSmall) - CompositionLocalProvider(LocalTheme provides theme) { - LocaleProvider { - MaterialTheme( - colors = colors, - typography = Typography, - content = content - ) + Text("titleLarge", style = MaterialTheme.typography.titleLarge) + Text("titleMedium", style = MaterialTheme.typography.titleMedium) + Text("titleSmall", style = MaterialTheme.typography.titleSmall) + + Text("bodyLarge", style = MaterialTheme.typography.bodyLarge) + Text("bodyMedium", style = MaterialTheme.typography.bodyMedium) + Text("bodySmall", style = MaterialTheme.typography.bodySmall) + + Text("labelLarge", style = MaterialTheme.typography.labelLarge) + Text("labelMedium", style = MaterialTheme.typography.labelMedium) + Text("labelSmall", style = MaterialTheme.typography.labelSmall) + } + } + ComponentPreview("Buttons") { + Button(onClick = {}) { + Text("Filled") + } + Button(onClick = {}, enabled = false) { + Text("Disabled") + } + OutlinedButton(onClick = {}) { + Text("Outlined") + } + TextButton(onClick = {}) { + Text("Text") + } + } + ComponentPreview("Icon Buttons") { + IconButton(onClick = {}) { + Icon(Icons.Default.Map, contentDescription = "Icon Button") + } + } + ComponentPreview("Chip") { + AssistChip(onClick = {}, label = { + Text("Assist Chip") + }) + FilterChip(selected = false, onClick = {}, label = { + Text("Filter not Selected") + }) + FilterChip(selected = true, onClick = {}, label = { + Text("Filter Selected") + }) + } + ComponentPreview("Progress Indicator") { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)){ + CircularProgressIndicator() + LinearProgressIndicator() + } + } + ComponentPreview("Radio Button") { + var state by remember { mutableStateOf(true) } + RadioButton(!state, onClick = { state = false }) + RadioButton(state, onClick = { state = true }) + + } + ComponentPreview("Checkbox") { + var state by remember { mutableStateOf(true) } + Checkbox(state, onCheckedChange = { state = it }) + Checkbox(!state, onCheckedChange = { state = !it }) + Checkbox(state, onCheckedChange = {}, enabled = false) + TriStateCheckbox(ToggleableState.Indeterminate, onClick = {}) + } + ComponentPreview("Switch") { + var state by remember { mutableStateOf(true) } + Switch(state, onCheckedChange = { state = it }) + Switch(!state, enabled = false, onCheckedChange = { state = it }) + } + ComponentPreview("Slider") { + Column{ + var state by remember { mutableStateOf(0.5f) } + Slider(state, onValueChange = { state = it }) + var rangeState by remember { mutableStateOf(0.25f..0.75f) } + RangeSlider(rangeState, onValueChange = { rangeState = it }) + } + + } + ComponentPreview("Badge") { + IconButton(onClick = {}) { + BadgedBox(badge = { Badge() }) { + Icon(Icons.Default.Map, contentDescription = "Icon with Badge") + } + } + } + ComponentPreview("Number Field") { + var number by remember { mutableStateOf("123") } + TextField(number, onValueChange = { + if(it.all { char -> char.isDigit() }) { + number = it + } + }, label = { Text("Number Field") }) + + } + ComponentPreview("Text Field") { + Row { + var text by remember { mutableStateOf("Text Field") } + TextField(text, onValueChange = { text = it }) + } + var text by remember { mutableStateOf("Outlined Text Field") } + OutlinedTextField(text, onValueChange = { text = it}) + } + ComponentPreview("Dropdown Menu") { + var show by remember { mutableStateOf(false) } + AssistChip( + onClick = { show = true }, + label = { Text("Show Menu") } + ) + DropdownMenu( + expanded = show, + onDismissRequest = { + show = false + }, + ) { + DropdownMenuItem(onClick = { show = false }, text = { + Text("Menu Item 1", modifier = Modifier.padding(8.dp)) + }) + DropdownMenuItem(onClick = { show = false }, text = { + Text("Menu Item 2", modifier = Modifier.padding(8.dp)) + }) + DropdownMenuItem(onClick = { show = false }, text = { + Text("Menu Item 3", modifier = Modifier.padding(8.dp)) + }) + } + + + } + + ComponentPreview("Scrollable View") { + + } + + ComponentPreview("Tabs") { + + } + } + } } } } } -fun String.toColorInt(): Int { - if (this[0] == '#') { - var color = substring(1).toLong(16) - if (length == 7) { - color = color or 0x00000000ff000000L - } else if (length != 9) { - throw IllegalArgumentException("Unknown color") +@Composable +private fun ComponentPreview(title: String, content: @Composable () -> Unit) { + Column { + Text(title, style = MaterialTheme.typography.titleLarge) + HorizontalDivider() + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(vertical = 8.dp)) { + content() } - return color.toInt() + HorizontalDivider() } - throw IllegalArgumentException("Unknown color") } \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Typography.kt b/app/src/processing/app/ui/theme/Typography.kt index 5d87c490e6..6650ac7167 100644 --- a/app/src/processing/app/ui/theme/Typography.kt +++ b/app/src/processing/app/ui/theme/Typography.kt @@ -1,6 +1,5 @@ package processing.app.ui.theme -import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily @@ -21,18 +20,108 @@ val processingFont = FontFamily( style = FontStyle.Normal ) ) +val spaceGroteskFont = FontFamily( + Font( + resource = "SpaceGrotesk-Bold.ttf", + weight = FontWeight.Bold, + ), + Font( + resource = "SpaceGrotesk-Regular.ttf", + weight = FontWeight.Normal, + ), + Font( + resource = "SpaceGrotesk-Medium.ttf", + weight = FontWeight.Medium, + ), + Font( + resource = "SpaceGrotesk-SemiBold.ttf", + weight = FontWeight.SemiBold, + ), + Font( + resource = "SpaceGrotesk-Light.ttf", + weight = FontWeight.Light, + ) +) -val Typography = Typography( +@Deprecated("Use PDE3Typography instead") +val PDE2Typography = Typography( + defaultFontFamily = spaceGroteskFont, + h1 = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 42.725.sp, + lineHeight = 48.sp + ), + h2 = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 34.18.sp, + lineHeight = 40.sp + ), + h3 = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 27.344.sp, + lineHeight = 32.sp + ), + h4 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 21.875.sp, + lineHeight = 28.sp + ), + h5 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 17.5.sp, + lineHeight = 22.sp + ), + h6 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 18.sp + ), body1 = TextStyle( - fontFamily = processingFont, fontWeight = FontWeight.Normal, - fontSize = 13.sp, + fontSize = 14.sp, + lineHeight = 18.sp + ), + body2 = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 12.8.sp, lineHeight = 16.sp ), subtitle1 = TextStyle( - fontFamily = processingFont, - fontWeight = FontWeight.Bold, + fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 20.sp + ), + subtitle2 = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 13.824.sp, + lineHeight = 16.sp, + ), + caption = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 11.2.sp, + lineHeight = 14.sp + ), + overline = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 8.96.sp, + lineHeight = 10.sp ) +) +val base = androidx.compose.material3.Typography() +val PDETypography = androidx.compose.material3.Typography( + displayLarge = base.displayLarge.copy(fontFamily = spaceGroteskFont), + displayMedium = base.displayMedium.copy(fontFamily = spaceGroteskFont), + displaySmall = base.displaySmall.copy(fontFamily = spaceGroteskFont), + headlineLarge = base.headlineLarge.copy(fontFamily = spaceGroteskFont), + headlineMedium = base.headlineMedium.copy(fontFamily = spaceGroteskFont), + headlineSmall = base.headlineSmall.copy(fontFamily = spaceGroteskFont), + titleLarge = base.titleLarge.copy(fontFamily = spaceGroteskFont), + titleMedium = base.titleMedium.copy(fontFamily = spaceGroteskFont), + titleSmall = base.titleSmall.copy(fontFamily = spaceGroteskFont), + bodyLarge = base.bodyLarge.copy(fontFamily = spaceGroteskFont), + bodyMedium = base.bodyMedium.copy(fontFamily = spaceGroteskFont), + bodySmall = base.bodySmall.copy(fontFamily = spaceGroteskFont), + labelLarge = base.labelLarge.copy(fontFamily = spaceGroteskFont), + labelMedium = base.labelMedium.copy(fontFamily = spaceGroteskFont), + labelSmall = base.labelSmall.copy(fontFamily = spaceGroteskFont), ) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 050502f4ca..b1cafb2d2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -kotlin = "2.0.20" -compose-plugin = "1.7.1" +kotlin = "2.2.20" +compose-plugin = "1.9.1" jogl = "2.5.0" antlr = "4.13.2" jupiter = "5.12.0" @@ -35,10 +35,10 @@ markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" } clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" } kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } +material3 = { module = "org.jetbrains.compose.material3:material3", version = "1.9.0" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } download = { id = "de.undercouch.download", version = "5.6.0" } From 408b87f6f3313a6f052fa589f3f32d031ad4066c Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 10:38:55 +0200 Subject: [PATCH 38/50] Refactor to use Material3 and update theme usage Replaces Material2 components with Material3 in WelcomeToBeta, removes custom PDEButton in favor of Material3 Button, and updates theme usage to PDETheme. Also simplifies background modifier in PDETheme and removes unused Kotlin Multiplatform plugin from build.gradle.kts. --- app/src/processing/app/ui/WelcomeToBeta.kt | 73 +++++----------------- app/src/processing/app/ui/theme/Theme.kt | 2 +- build.gradle.kts | 1 - 3 files changed, 15 insertions(+), 61 deletions(-) diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index 7757e820f6..e18c97c3f1 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -5,11 +5,11 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme -import androidx.compose.material.MaterialTheme.colors -import androidx.compose.material.MaterialTheme.typography -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi @@ -39,9 +39,8 @@ import processing.app.Preferences import processing.app.Base.getRevision import processing.app.Base.getVersionName import processing.app.ui.theme.LocalLocale -import processing.app.ui.theme.LocalTheme import processing.app.ui.theme.Locale -import processing.app.ui.theme.ProcessingTheme +import processing.app.ui.theme.PDETheme import java.awt.Cursor import java.awt.Dimension import java.awt.event.KeyAdapter @@ -72,7 +71,7 @@ class WelcomeToBeta { contentPane.add(ComposePanel().apply { size = windowSize setContent { - ProcessingTheme { + PDETheme(darkTheme = false) { Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { welcomeToBeta(close) } @@ -123,7 +122,7 @@ class WelcomeToBeta { ) { Text( text = locale["beta.title"], - style = typography.subtitle1, + style = typography.titleLarge, ) val text = locale["beta.message"] .replace('$' + "version", getVersionName()) @@ -131,65 +130,23 @@ class WelcomeToBeta { Markdown( text, colors = markdownColor(), - typography = markdownTypography(text = typography.body1, link = typography.body1.copy(color = colors.primary)), + typography = markdownTypography(text = typography.bodyMedium, link = typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary)), modifier = Modifier.background(Color.Transparent).padding(bottom = 10.dp) ) Row { Spacer(modifier = Modifier.weight(1f)) - PDEButton(onClick = { + Button(onClick = { close() }) { Text( text = locale["beta.button"], - color = colors.onPrimary + color = MaterialTheme.colorScheme.onPrimary ) } } } } } - @OptIn(ExperimentalComposeUiApi::class) - @Composable - fun PDEButton(onClick: () -> Unit, content: @Composable BoxScope.() -> Unit) { - val theme = LocalTheme.current - - var hover by remember { mutableStateOf(false) } - var clicked by remember { mutableStateOf(false) } - val offset by animateFloatAsState(if (hover) -5f else 5f) - val color by animateColorAsState(if(clicked) colors.primaryVariant else colors.primary) - - Box(modifier = Modifier.padding(end = 5.dp, top = 5.dp)) { - Box( - modifier = Modifier - .offset((-offset).dp, (offset).dp) - .background(theme.getColor("toolbar.button.pressed.field")) - .matchParentSize() - ) - Box( - modifier = Modifier - .onPointerEvent(PointerEventType.Press) { - clicked = true - } - .onPointerEvent(PointerEventType.Release) { - clicked = false - onClick() - } - .onPointerEvent(PointerEventType.Enter) { - hover = true - } - .onPointerEvent(PointerEventType.Exit) { - hover = false - } - .pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR))) - .background(color) - .padding(10.dp) - .sizeIn(minWidth = 100.dp), - contentAlignment = Alignment.Center, - content = content - ) - } - } - @JvmStatic fun main(args: Array) { @@ -200,11 +157,9 @@ class WelcomeToBeta { ) Window(onCloseRequest = ::exitApplication, state = windowState, title = windowTitle) { - ProcessingTheme { - Surface(color = colors.background) { - welcomeToBeta { - exitApplication() - } + PDETheme(darkTheme = false) { + welcomeToBeta { + exitApplication() } } } diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 7cc70455f0..9e41227ed1 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -93,7 +93,7 @@ fun PDETheme( colorScheme = if(darkTheme) PDEDarkColor else PDELightColor, typography = PDETypography ){ - Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { + Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) { CompositionLocalProvider( LocalContentColor provides MaterialTheme.colorScheme.onBackground, LocalDensity provides Density(1.25f, 1.25f), diff --git a/build.gradle.kts b/build.gradle.kts index 8e7ad44a7a..6c8c5262cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ plugins { kotlin("jvm") version libs.versions.kotlin apply false - alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.jetbrainsCompose) apply false From 38a990f811ba5a4882836bc2772598b223bd9b1b Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 10:39:19 +0200 Subject: [PATCH 39/50] Add Space Grotesk font files and license Includes SpaceGrotesk font variants (Bold, Light, Medium, Regular, SemiBold) and the associated SIL Open Font License. This enables usage of the Space Grotesk typeface in the project. --- build/shared/lib/fonts/SpaceGrotesk-Bold.ttf | Bin 0 -> 86520 bytes .../shared/lib/fonts/SpaceGrotesk-LICENSE.txt | 93 ++++++++++++++++++ build/shared/lib/fonts/SpaceGrotesk-Light.ttf | Bin 0 -> 86616 bytes .../shared/lib/fonts/SpaceGrotesk-Medium.ttf | Bin 0 -> 86616 bytes .../shared/lib/fonts/SpaceGrotesk-Regular.ttf | Bin 0 -> 86592 bytes .../lib/fonts/SpaceGrotesk-SemiBold.ttf | Bin 0 -> 86576 bytes 6 files changed, 93 insertions(+) create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Bold.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-LICENSE.txt create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Light.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Medium.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-Regular.ttf create mode 100644 build/shared/lib/fonts/SpaceGrotesk-SemiBold.ttf diff --git a/build/shared/lib/fonts/SpaceGrotesk-Bold.ttf b/build/shared/lib/fonts/SpaceGrotesk-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0408641c61d0f3ce53d24348e9db72dcc1261179 GIT binary patch literal 86520 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 literal 0 HcmV?d00001 diff --git a/build/shared/lib/fonts/SpaceGrotesk-Regular.ttf b/build/shared/lib/fonts/SpaceGrotesk-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..981bcf5b2cb0a1ca43dcee7577015c0bfbdf91fb GIT binary patch literal 86592 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 Sp;;r@Vp7<@IS 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 literal 0 HcmV?d00001 diff --git a/build/shared/lib/fonts/SpaceGrotesk-SemiBold.ttf b/build/shared/lib/fonts/SpaceGrotesk-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7e02e51e48209e508954e9fdc08a1036d752702 GIT binary patch literal 86576 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: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 WBn3EMsDPe7sg0 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 Date: Tue, 21 Oct 2025 10:47:56 +0200 Subject: [PATCH 40/50] Update markdown renderer to m3 and adjust UI Switched markdown renderer imports from m2 to m3 and updated the dependency version to 0.37.0. Adjusted WelcomeToBeta window size, layout, and logo dimensions for improved appearance. Ensured Box in Theme.kt fills available space for better layout consistency. --- app/src/processing/app/ui/WelcomeToBeta.kt | 14 +++++++------- app/src/processing/app/ui/theme/Theme.kt | 2 +- gradle/libs.versions.toml | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index e18c97c3f1..2725a78176 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -31,8 +31,8 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import com.formdev.flatlaf.util.SystemInfo import com.mikepenz.markdown.compose.Markdown -import com.mikepenz.markdown.m2.markdownColor -import com.mikepenz.markdown.m2.markdownTypography +import com.mikepenz.markdown.m3.markdownColor +import com.mikepenz.markdown.m3.markdownTypography import com.mikepenz.markdown.model.MarkdownColors import com.mikepenz.markdown.model.MarkdownTypography import processing.app.Preferences @@ -53,7 +53,7 @@ import javax.swing.SwingUtilities class WelcomeToBeta { companion object{ - val windowSize = Dimension(400, 200) + val windowSize = Dimension(400, 250) val windowTitle = Locale()["beta.window.title"] @JvmStatic @@ -98,7 +98,7 @@ class WelcomeToBeta { Row( modifier = Modifier .padding(20.dp, 10.dp) - .size(windowSize.width.dp, windowSize.height.dp), + .fillMaxSize(), horizontalArrangement = Arrangement .spacedBy(20.dp) ){ @@ -108,7 +108,7 @@ class WelcomeToBeta { contentDescription = locale["beta.logo"], modifier = Modifier .align(Alignment.CenterVertically) - .size(100.dp, 100.dp) + .size(120.dp) .offset(0.dp, (-25).dp) ) Column( @@ -130,7 +130,7 @@ class WelcomeToBeta { Markdown( text, colors = markdownColor(), - typography = markdownTypography(text = typography.bodyMedium, link = typography.bodyMedium.copy(color = MaterialTheme.colorScheme.primary)), + typography = markdownTypography(), modifier = Modifier.background(Color.Transparent).padding(bottom = 10.dp) ) Row { @@ -152,7 +152,7 @@ class WelcomeToBeta { fun main(args: Array) { application { val windowState = rememberWindowState( - size = DpSize.Unspecified, + size = windowSize.let { DpSize(it.width.dp, it.height.dp) }, position = WindowPosition(Alignment.Center) ) diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 9e41227ed1..7cc70455f0 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -93,7 +93,7 @@ fun PDETheme( colorScheme = if(darkTheme) PDEDarkColor else PDELightColor, typography = PDETypography ){ - Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) { + Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { CompositionLocalProvider( LocalContentColor provides MaterialTheme.colorScheme.onBackground, LocalDensity provides Density(1.25f, 1.25f), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1cafb2d2f..a2a3edacc0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ compose-plugin = "1.9.1" jogl = "2.5.0" antlr = "4.13.2" jupiter = "5.12.0" +markdown = "0.37.0" [libraries] jogl = { module = "org.jogamp.jogl:jogl-all-main", version.ref = "jogl" } @@ -31,8 +32,8 @@ antlr4Runtime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr" } composeGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-plugin" } kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlinComposePlugin = { module = "org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin", version.ref = "kotlin" } -markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version = "0.31.0" } -markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" } +markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "markdown" } +markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version.ref = "markdown" } clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" } kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } material3 = { module = "org.jetbrains.compose.material3:material3", version = "1.9.0" } From b2f953f7133c40bc498f38a16b9ef94a9ac43118 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 11:37:28 +0200 Subject: [PATCH 41/50] Add Preferences UI with Compose and NavigationRail Introduces a new Preferences screen using Jetpack Compose, featuring a NavigationRail for settings categories, a search bar, and placeholder content. Includes main entry point for launching the preferences window in both dark and light themes. --- app/src/processing/app/ui/Preferences.kt | 118 +++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 app/src/processing/app/ui/Preferences.kt diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt new file mode 100644 index 0000000000..acf7aeb215 --- /dev/null +++ b/app/src/processing/app/ui/Preferences.kt @@ -0,0 +1,118 @@ +package processing.app.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.NavigationRail +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import processing.app.ui.theme.PDETheme + +class Preferences { + companion object{ + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun preferences(){ + Row { + NavigationRail( + header = { + Text("Settings", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(top = 42.dp)) + + }, + modifier = Modifier.defaultMinSize(minWidth = 200.dp) + ) { + NavigationRailItem( + selected = true, + onClick = { /*TODO*/ }, + icon = { + Icon(Icons.Default.Settings, contentDescription = null) + }, + label = { + Text("General") + } + ) + NavigationRailItem( + selected = false, + onClick = { /*TODO*/ }, + icon = { + Icon(Icons.Default.Settings, contentDescription = null) + }, + label = { + Text("Interface & fonts") + } + ) + } + Box(modifier = Modifier.padding(top = 42.dp)){ + Column(modifier = Modifier.fillMaxSize()) { + SearchBar(inputField = { + SearchBarDefaults.InputField( + query = "", + onQueryChange = { }, + onSearch = { + + }, + expanded = false, + onExpandedChange = { }, + placeholder = { Text("Search") } + ) + }, expanded = false, onExpandedChange = {}, modifier = Modifier.align(Alignment.End).padding(16.dp)) { + + } + Text("Preferences go here") + } + } + } + } + + @JvmStatic + fun main(args: Array) { + + application { + Window(onCloseRequest = ::exitApplication){ + remember{ + window.rootPane.putClientProperty("apple.awt.fullWindowContent", true) + window.rootPane.putClientProperty("apple.awt.transparentTitleBar", true) + } + PDETheme(darkTheme = true) { + preferences() + } + } + Window(onCloseRequest = ::exitApplication){ + remember{ + window.rootPane.putClientProperty("apple.awt.fullWindowContent", true) + window.rootPane.putClientProperty("apple.awt.transparentTitleBar", true) + } + PDETheme(darkTheme = false) { + preferences() + } + } + + } + + } + } +} \ No newline at end of file From 490c3a539b53cb6b10379d0d825437186545c6b2 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 14:45:35 +0200 Subject: [PATCH 42/50] Refactor preferences UI and add General group Reworked Preferences UI to support dynamic preference groups and searching. Added new General and Other preference groups with controls, and updated language properties for advanced preferences. Introduced PDEPreference and PDEPreferenceGroup data classes for extensibility. --- app/src/processing/app/ui/Preferences.kt | 213 +++++++++++++----- .../processing/app/ui/preferences/General.kt | 109 +++++++++ build/shared/lib/languages/PDE.properties | 1 + 3 files changed, 272 insertions(+), 51 deletions(-) create mode 100644 app/src/processing/app/ui/preferences/General.kt diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index acf7aeb215..bbd966e466 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -6,83 +6,169 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.DrawerValue +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Text -import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.debounce +import processing.app.LocalPreferences +import processing.app.ui.preferences.General +import processing.app.ui.theme.LocalLocale import processing.app.ui.theme.PDETheme +val LocalPreferenceGroups = compositionLocalOf>> { + error("No Preference Groups Set") +} + class Preferences { companion object{ + val groups = mutableStateMapOf>() + fun register(preference: PDEPreference) { + val list = groups[preference.group]?.toMutableList() ?: mutableListOf() + list.add(preference) + groups[preference.group] = list + } + init{ + General.register() + } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun preferences(){ - Row { - NavigationRail( - header = { - Text("Settings", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(top = 42.dp)) + var visible by remember { mutableStateOf(groups) } + val sortedGroups = remember { + val keys = visible.keys + keys.toSortedSet { + a, b -> + when { + a.after == b -> 1 + b.after == a -> -1 + else -> a.name.compareTo(b.name) + } + } + } + var selected by remember { mutableStateOf(sortedGroups.first()) } + CompositionLocalProvider( + LocalPreferenceGroups provides visible + ) { + Row { + NavigationRail( + header = { + Text( + "Settings", + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 42.dp) + ) - }, - modifier = Modifier.defaultMinSize(minWidth = 200.dp) - ) { - NavigationRailItem( - selected = true, - onClick = { /*TODO*/ }, - icon = { - Icon(Icons.Default.Settings, contentDescription = null) - }, - label = { - Text("General") - } - ) - NavigationRailItem( - selected = false, - onClick = { /*TODO*/ }, - icon = { - Icon(Icons.Default.Settings, contentDescription = null) }, - label = { - Text("Interface & fonts") + modifier = Modifier.defaultMinSize(minWidth = 200.dp) + ) { + + for (group in sortedGroups) { + NavigationRailItem( + selected = selected == group, + enabled = visible.keys.contains(group), + onClick = { + selected = group + }, + icon = { + group.icon() + }, + label = { + Text(group.name) + } + ) } - ) - } - Box(modifier = Modifier.padding(top = 42.dp)){ - Column(modifier = Modifier.fillMaxSize()) { - SearchBar(inputField = { - SearchBarDefaults.InputField( - query = "", - onQueryChange = { }, - onSearch = { + } + Box(modifier = Modifier.padding(top = 42.dp)) { + Column(modifier = Modifier.fillMaxSize()) { + var query by remember { mutableStateOf("") } + val locale = LocalLocale.current + LaunchedEffect(query){ + + snapshotFlow { query } + .debounce(100) + .collect{ + if(it.isBlank()){ + visible = groups + return@collect + } + val filtered = mutableStateMapOf>() + for((group, preferences) in groups){ + val matching = preferences.filter { preference -> + if(preference.key.contains(it, ignoreCase = true)){ + return@filter true + } + val description = locale[preference.descriptionKey] + description.contains(it, ignoreCase = true) + } + if(matching.isNotEmpty()){ + filtered[group] = matching + } + } + visible = filtered + } + + } + SearchBar( + inputField = { + SearchBarDefaults.InputField( + query = query, + onQueryChange = { + query = it + }, + onSearch = { + }, + expanded = false, + onExpandedChange = { }, + placeholder = { Text("Search") } + ) }, expanded = false, - onExpandedChange = { }, - placeholder = { Text("Search") } - ) - }, expanded = false, onExpandedChange = {}, modifier = Modifier.align(Alignment.End).padding(16.dp)) { + onExpandedChange = {}, + modifier = Modifier.align(Alignment.End).padding(16.dp) + ) { + } + val prefs = LocalPreferences.current + val preferences = visible[selected] ?: emptyList() + LazyColumn { + items(preferences){ preference -> + Text( + text = locale[preference.descriptionKey], + modifier = Modifier.padding(20.dp), + style = MaterialTheme.typography.titleMedium + ) + preference.control(prefs[preference.key]) { newValue -> + prefs[preference.key] = newValue + } + } + } } - Text("Preferences go here") } } } @@ -90,7 +176,6 @@ class Preferences { @JvmStatic fun main(args: Array) { - application { Window(onCloseRequest = ::exitApplication){ remember{ @@ -110,9 +195,35 @@ class Preferences { preferences() } } - } - } } -} \ No newline at end of file +} + +data class PDEPreference( + /** + * The key in the preferences file used to store this preference. + */ + val key: String, + val descriptionKey: String, + /** + * The group this preference belongs to. + */ + val group: PDEPreferenceGroup, + val control: @Composable (preference: String?, updatePreference: (newValue: String) -> Unit) -> Unit = { preference, updatePreference -> }, +) + +data class PDEPreferenceGroup( + /** + * The name of this group. + */ + val name: String, + /** + * The icon representing this group. + */ + val icon: @Composable () -> Unit, + /** + * The group that comes before this one in the list. + */ + val after: PDEPreferenceGroup? = null, +) \ No newline at end of file diff --git a/app/src/processing/app/ui/preferences/General.kt b/app/src/processing/app/ui/preferences/General.kt new file mode 100644 index 0000000000..bffac1765c --- /dev/null +++ b/app/src/processing/app/ui/preferences/General.kt @@ -0,0 +1,109 @@ +package processing.app.ui.preferences + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Map +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import processing.app.LocalPreferences +import processing.app.ui.LocalPreferenceGroups +import processing.app.ui.PDEPreference +import processing.app.ui.PDEPreferenceGroup +import processing.app.ui.Preferences +import processing.app.ui.theme.LocalLocale + + +class General { + companion object{ + val general = PDEPreferenceGroup( + name = "General", + icon = { + Icon(Icons.Default.Settings, contentDescription = "A settings icon") + } + ) + val other = PDEPreferenceGroup( + name = "Other", + icon = { + Icon(Icons.Default.Map, contentDescription = "A map icon") + }, + after = general + ) + fun register() { + Preferences.register( + PDEPreference( + key = "sketchbook.path.four", + descriptionKey = "preferences.sketchbook_location", + group = general, + control = { preference, updatePreference -> + Row ( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + TextField( + value = preference ?: "", + onValueChange = { + updatePreference(it) + } + ) + Button( + onClick = { + + } + ) { + Text("Browse") + } + } + } + ) + ) + Preferences.register( + PDEPreference( + key = "other", + descriptionKey = "preferences.other", + group = other, + control = { _, _ -> + val prefs = LocalPreferences.current + val groups = LocalPreferenceGroups.current + val restPrefs = remember { + val keys = prefs.keys.mapNotNull { it as? String } + val existing = groups.values.flatten().map { it.key } + keys.filter { it !in existing }.sorted() + } + val locale = LocalLocale.current + + for(prefKey in restPrefs){ + val value = prefs[prefKey] + Row ( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ){ + Text( + text = locale[prefKey], + modifier = Modifier.align(Alignment.CenterVertically) + ) + TextField(value ?: "", onValueChange = { + prefs[prefKey] = it + }) + } + } + + } + ) + ) + } + } +} \ No newline at end of file diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index ae3fcd6c60..85241406bb 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -238,6 +238,7 @@ preferences.launch_programs_in = Launch programs in preferences.launch_programs_in.mode = mode preferences.file = More preferences can be edited directly in the file: preferences.file.hint = (Edit only when Processing is not running.) +preferences.other = Other Preferences (advanced users) # Sketchbook Location (Frame) sketchbook_location = Select new sketchbook folder From e664b2c8e1d5407b8ed8b251d0293d996a67bbf3 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 21 Oct 2025 15:11:41 +0200 Subject: [PATCH 43/50] Add new general preferences options Introduces preferences for sketch naming approach, update check on startup, and welcome screen visibility. Also improves UI spacing and filtering logic in Preferences screen. --- app/src/processing/app/ui/Preferences.kt | 9 ++- .../processing/app/ui/preferences/General.kt | 67 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index bbd966e466..af745c7a42 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -1,5 +1,6 @@ package processing.app.ui +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem @@ -119,6 +121,9 @@ class Preferences { val filtered = mutableStateMapOf>() for((group, preferences) in groups){ val matching = preferences.filter { preference -> + if(preference.key == "other"){ + return@filter true + } if(preference.key.contains(it, ignoreCase = true)){ return@filter true } @@ -156,11 +161,11 @@ class Preferences { } val prefs = LocalPreferences.current val preferences = visible[selected] ?: emptyList() - LazyColumn { + LazyColumn(verticalArrangement = Arrangement.spacedBy(20.dp)) { items(preferences){ preference -> Text( text = locale[preference.descriptionKey], - modifier = Modifier.padding(20.dp), + modifier = Modifier.padding(horizontal = 20.dp), style = MaterialTheme.typography.titleMedium ) preference.control(prefs[preference.key]) { newValue -> diff --git a/app/src/processing/app/ui/preferences/General.kt b/app/src/processing/app/ui/preferences/General.kt index bffac1765c..724121ca75 100644 --- a/app/src/processing/app/ui/preferences/General.kt +++ b/app/src/processing/app/ui/preferences/General.kt @@ -7,8 +7,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Map import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.AssistChip import androidx.compose.material3.Button +import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.remember @@ -16,6 +19,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import processing.app.LocalPreferences +import processing.app.SketchName import processing.app.ui.LocalPreferenceGroups import processing.app.ui.PDEPreference import processing.app.ui.PDEPreferenceGroup @@ -68,6 +72,69 @@ class General { } ) ) + Preferences.register( + PDEPreference( + key = "sketch.name.approach", + descriptionKey = "preferences.sketch_naming", + group = general, + control = { preference, updatePreference -> + Row( + modifier = Modifier.padding(horizontal = 20.dp) + ) { + for (option in if (false) SketchName.getOptions() else arrayOf( + "timestamp", + "untitled", + "custom" + )) { + FilterChip( + selected = preference == option, + onClick = { + updatePreference(option) + }, + label = { + Text(option) + }, + modifier = Modifier.padding(4.dp), + ) + } + } + } + ) + ) + Preferences.register( + PDEPreference( + key = "update.check", + descriptionKey = "preferences.check_for_updates_on_startup", + group = general, + control = { preference, updatePreference -> + Switch( + checked = preference.toBoolean(), + modifier = Modifier.padding(horizontal = 20.dp), + onCheckedChange = { + updatePreference(it.toString()) + } + ) + } + ) + ) + // show welcome screen + Preferences.register( + PDEPreference( + key = "welcome.show", + descriptionKey = "preferences.show_welcome_screen_on_startup", + group = general, + control = { preference, updatePreference -> + Switch( + checked = preference.toBoolean(), + modifier = Modifier.padding(horizontal = 20.dp), + onCheckedChange = { + updatePreference(it.toString()) + } + ) + } + ) + ) + Preferences.register( PDEPreference( key = "other", From 2f094ebc8f08b7eaafab37786a57e74f2061fcab Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 22 Oct 2025 08:49:13 +0200 Subject: [PATCH 44/50] Refactor preferences and add Other group Renamed Preferences to PDEPreferences and moved the 'Other' preference group logic from General.kt to a new Other.kt file for better separation. Registered the Other group in PDEPreferences to improve organization and maintainability of preference groups. --- app/src/processing/app/ui/Preferences.kt | 5 +- .../processing/app/ui/preferences/General.kt | 63 ++-------------- .../processing/app/ui/preferences/Other.kt | 72 +++++++++++++++++++ 3 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 app/src/processing/app/ui/preferences/Other.kt diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index af745c7a42..5494e0654a 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import processing.app.LocalPreferences import processing.app.ui.preferences.General +import processing.app.ui.preferences.Other import processing.app.ui.theme.LocalLocale import processing.app.ui.theme.PDETheme @@ -44,7 +45,7 @@ val LocalPreferenceGroups = compositionLocalOf>() fun register(preference: PDEPreference) { @@ -54,9 +55,9 @@ class Preferences { } init{ General.register() + Other.register() } - @OptIn(ExperimentalMaterial3Api::class) @Composable fun preferences(){ diff --git a/app/src/processing/app/ui/preferences/General.kt b/app/src/processing/app/ui/preferences/General.kt index 724121ca75..1838e0adb3 100644 --- a/app/src/processing/app/ui/preferences/General.kt +++ b/app/src/processing/app/ui/preferences/General.kt @@ -5,26 +5,19 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Map import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.AssistChip import androidx.compose.material3.Button import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import processing.app.LocalPreferences import processing.app.SketchName -import processing.app.ui.LocalPreferenceGroups import processing.app.ui.PDEPreference import processing.app.ui.PDEPreferenceGroup -import processing.app.ui.Preferences -import processing.app.ui.theme.LocalLocale +import processing.app.ui.PDEPreferences class General { @@ -35,15 +28,9 @@ class General { Icon(Icons.Default.Settings, contentDescription = "A settings icon") } ) - val other = PDEPreferenceGroup( - name = "Other", - icon = { - Icon(Icons.Default.Map, contentDescription = "A map icon") - }, - after = general - ) + fun register() { - Preferences.register( + PDEPreferences.register( PDEPreference( key = "sketchbook.path.four", descriptionKey = "preferences.sketchbook_location", @@ -72,7 +59,7 @@ class General { } ) ) - Preferences.register( + PDEPreferences.register( PDEPreference( key = "sketch.name.approach", descriptionKey = "preferences.sketch_naming", @@ -101,7 +88,7 @@ class General { } ) ) - Preferences.register( + PDEPreferences.register( PDEPreference( key = "update.check", descriptionKey = "preferences.check_for_updates_on_startup", @@ -117,8 +104,7 @@ class General { } ) ) - // show welcome screen - Preferences.register( + PDEPreferences.register( PDEPreference( key = "welcome.show", descriptionKey = "preferences.show_welcome_screen_on_startup", @@ -134,43 +120,6 @@ class General { } ) ) - - Preferences.register( - PDEPreference( - key = "other", - descriptionKey = "preferences.other", - group = other, - control = { _, _ -> - val prefs = LocalPreferences.current - val groups = LocalPreferenceGroups.current - val restPrefs = remember { - val keys = prefs.keys.mapNotNull { it as? String } - val existing = groups.values.flatten().map { it.key } - keys.filter { it !in existing }.sorted() - } - val locale = LocalLocale.current - - for(prefKey in restPrefs){ - val value = prefs[prefKey] - Row ( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - ){ - Text( - text = locale[prefKey], - modifier = Modifier.align(Alignment.CenterVertically) - ) - TextField(value ?: "", onValueChange = { - prefs[prefKey] = it - }) - } - } - - } - ) - ) } } } \ No newline at end of file diff --git a/app/src/processing/app/ui/preferences/Other.kt b/app/src/processing/app/ui/preferences/Other.kt new file mode 100644 index 0000000000..29062f5991 --- /dev/null +++ b/app/src/processing/app/ui/preferences/Other.kt @@ -0,0 +1,72 @@ +package processing.app.ui.preferences + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Map +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import processing.app.LocalPreferences +import processing.app.ui.LocalPreferenceGroups +import processing.app.ui.PDEPreference +import processing.app.ui.PDEPreferenceGroup +import processing.app.ui.PDEPreferences +import processing.app.ui.preferences.General.Companion.general +import processing.app.ui.theme.LocalLocale + +class Other { + companion object{ + val other = PDEPreferenceGroup( + name = "Other", + icon = { + Icon(Icons.Default.Map, contentDescription = "A map icon") + }, + after = general + ) + fun register() { + PDEPreferences.register( + PDEPreference( + key = "other", + descriptionKey = "preferences.other", + group = other, + control = { _, _ -> + val prefs = LocalPreferences.current + val groups = LocalPreferenceGroups.current + val restPrefs = remember { + val keys = prefs.keys.mapNotNull { it as? String } + val existing = groups.values.flatten().map { it.key } + keys.filter { it !in existing }.sorted() + } + val locale = LocalLocale.current + + for(prefKey in restPrefs){ + val value = prefs[prefKey] + Row ( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ){ + Text( + text = locale[prefKey], + modifier = Modifier.align(Alignment.CenterVertically) + ) + TextField(value ?: "", onValueChange = { + prefs[prefKey] = it + }) + } + } + + } + ) + ) + } + } +} \ No newline at end of file From 84941093d421c695877594a3badc9066d675062d Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 22 Oct 2025 09:56:42 +0200 Subject: [PATCH 45/50] Add Interface preferences group and UI improvements Introduces a new Interface preferences group with controls for language and font settings. Refactors preference UI rendering to support custom padding and improves composable structure. Updates General and Other preferences to use the new group and initialization checks. --- app/src/processing/app/Preferences.java | 8 + app/src/processing/app/ui/Preferences.kt | 50 +++++- .../processing/app/ui/preferences/General.kt | 3 +- .../app/ui/preferences/Interface.kt | 168 ++++++++++++++++++ .../processing/app/ui/preferences/Other.kt | 5 +- 5 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 app/src/processing/app/ui/preferences/Interface.kt diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java index 640c77eade..8fcf7bb056 100644 --- a/app/src/processing/app/Preferences.java +++ b/app/src/processing/app/Preferences.java @@ -136,6 +136,14 @@ static public void skipInit() { initialized = true; } + /** + * Check whether Preferences.init() has been called. If not, we are probably not running the full application. + * @return true if Preferences has been initialized + */ + static public boolean isInitialized() { + return initialized; + } + static void handleProxy(String protocol, String hostProp, String portProp) { String proxyHost = get("proxy." + protocol + ".host"); diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index 5494e0654a..af15caabd7 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import processing.app.LocalPreferences import processing.app.ui.preferences.General +import processing.app.ui.preferences.Interface import processing.app.ui.preferences.Other import processing.app.ui.theme.LocalLocale import processing.app.ui.theme.PDETheme @@ -55,6 +56,7 @@ class PDEPreferences { } init{ General.register() + Interface.register() Other.register() } @@ -160,18 +162,11 @@ class PDEPreferences { ) { } - val prefs = LocalPreferences.current + val preferences = visible[selected] ?: emptyList() LazyColumn(verticalArrangement = Arrangement.spacedBy(20.dp)) { items(preferences){ preference -> - Text( - text = locale[preference.descriptionKey], - modifier = Modifier.padding(horizontal = 20.dp), - style = MaterialTheme.typography.titleMedium - ) - preference.control(prefs[preference.key]) { newValue -> - prefs[preference.key] = newValue - } + preference.showControl() } } } @@ -211,14 +206,51 @@ data class PDEPreference( * The key in the preferences file used to store this preference. */ val key: String, + /** + * The key for the description of this preference, used for localization. + */ val descriptionKey: String, /** * The group this preference belongs to. */ val group: PDEPreferenceGroup, + /** + * A Composable function that defines the control used to modify this preference. + * It takes the current preference value and a function to update the preference. + */ val control: @Composable (preference: String?, updatePreference: (newValue: String) -> Unit) -> Unit = { preference, updatePreference -> }, + + /** + * If true, no padding will be applied around this preference's UI. + */ + val noPadding: Boolean = false, ) +@Composable +private fun PDEPreference.showControl() { + val locale = LocalLocale.current + val prefs = LocalPreferences.current + Text( + text = locale[descriptionKey], + modifier = Modifier.padding(horizontal = 20.dp), + style = MaterialTheme.typography.titleMedium + ) + val show = @Composable { + control(prefs[key]) { newValue -> + prefs[key] = newValue + } + } + + if(noPadding){ + show() + }else{ + Box(modifier = Modifier.padding(horizontal = 20.dp)) { + show() + } + } + +} + data class PDEPreferenceGroup( /** * The name of this group. diff --git a/app/src/processing/app/ui/preferences/General.kt b/app/src/processing/app/ui/preferences/General.kt index 1838e0adb3..18921a7474 100644 --- a/app/src/processing/app/ui/preferences/General.kt +++ b/app/src/processing/app/ui/preferences/General.kt @@ -14,6 +14,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import processing.app.Preferences import processing.app.SketchName import processing.app.ui.PDEPreference import processing.app.ui.PDEPreferenceGroup @@ -68,7 +69,7 @@ class General { Row( modifier = Modifier.padding(horizontal = 20.dp) ) { - for (option in if (false) SketchName.getOptions() else arrayOf( + for (option in if(Preferences.isInitialized()) SketchName.getOptions() else arrayOf( "timestamp", "untitled", "custom" diff --git a/app/src/processing/app/ui/preferences/Interface.kt b/app/src/processing/app/ui/preferences/Interface.kt new file mode 100644 index 0000000000..fc384fbc59 --- /dev/null +++ b/app/src/processing/app/ui/preferences/Interface.kt @@ -0,0 +1,168 @@ +package processing.app.ui.preferences + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.TextIncrease +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import processing.app.Language +import processing.app.Preferences +import processing.app.ui.PDEPreference +import processing.app.ui.PDEPreferenceGroup +import processing.app.ui.PDEPreferences +import processing.app.ui.Toolkit +import processing.app.ui.preferences.General.Companion.general +import processing.app.ui.theme.LocalLocale +import java.util.Locale + +class Interface { + companion object{ + val interfaceAndFonts = PDEPreferenceGroup( + name = "Interface", + icon = { + Icon(Icons.Default.TextIncrease, contentDescription = "Interface") + }, + after = general + ) + + fun register() { + PDEPreferences.register(PDEPreference( + key = "language", + descriptionKey = "preferences.language", + group = interfaceAndFonts, + control = { preference, updatePreference -> + val locale = LocalLocale.current + var showOptions by remember { mutableStateOf(false) } + val languages = if(Preferences.isInitialized()) Language.getLanguages() else mapOf("en" to "English") + TextField( + value = locale.locale.displayName, + readOnly = true, + onValueChange = { }, + trailingIcon = { + Icon( + Icons.Default.ArrowDropDown, + contentDescription = "Select Font Family", + modifier = Modifier + .clickable{ + showOptions = true + } + ) + } + ) + DropdownMenu( + expanded = showOptions, + onDismissRequest = { + showOptions = false + }, + ) { + languages.forEach { family -> + DropdownMenuItem( + text = { Text(family.value) }, + onClick = { + locale.set(Locale(family.key)) + showOptions = false + } + ) + } + } + } + )) + + PDEPreferences.register( + PDEPreference( + key = "editor.font.family", + descriptionKey = "preferences.editor_and_console_font", + group = interfaceAndFonts, + control = { preference, updatePreference -> + var showOptions by remember { mutableStateOf(false) } + val families = if(Preferences.isInitialized()) Toolkit.getMonoFontFamilies() else arrayOf("Monospaced") + TextField( + value = preference ?: families.firstOrNull().orEmpty(), + readOnly = true, + onValueChange = { updatePreference (it) }, + trailingIcon = { + Icon( + Icons.Default.ArrowDropDown, + contentDescription = "Select Font Family", + modifier = Modifier + .clickable{ + showOptions = true + } + ) + } + ) + DropdownMenu( + expanded = showOptions, + onDismissRequest = { + showOptions = false + }, + ) { + families.forEach { family -> + DropdownMenuItem( + text = { Text(family) }, + onClick = { + updatePreference(family) + showOptions = false + } + ) + } + + } + } + ) + ) + + PDEPreferences.register(PDEPreference( + key = "editor.font.size", + descriptionKey = "preferences.editor_font_size", + group = interfaceAndFonts, + control = { preference, updatePreference -> + Column { + Text( + text = "${preference ?: "12"} pt", + modifier = Modifier.width(120.dp) + ) + Slider( + value = (preference ?: "12").toFloat(), + onValueChange = { updatePreference(it.toInt().toString()) }, + valueRange = 10f..48f, + steps = 18, + ) + } + } + )) + PDEPreferences.register(PDEPreference( + key = "console.font.size", + descriptionKey = "preferences.console_font_size", + group = interfaceAndFonts, + control = { preference, updatePreference -> + Column { + Text( + text = "${preference ?: "12"} pt", + modifier = Modifier.width(120.dp) + ) + Slider( + value = (preference ?: "12").toFloat(), + onValueChange = { updatePreference(it.toInt().toString()) }, + valueRange = 10f..48f, + steps = 18, + ) + } + } + )) + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/preferences/Other.kt b/app/src/processing/app/ui/preferences/Other.kt index 29062f5991..f5f65ea9c8 100644 --- a/app/src/processing/app/ui/preferences/Other.kt +++ b/app/src/processing/app/ui/preferences/Other.kt @@ -18,7 +18,7 @@ import processing.app.ui.LocalPreferenceGroups import processing.app.ui.PDEPreference import processing.app.ui.PDEPreferenceGroup import processing.app.ui.PDEPreferences -import processing.app.ui.preferences.General.Companion.general +import processing.app.ui.preferences.Interface.Companion.interfaceAndFonts import processing.app.ui.theme.LocalLocale class Other { @@ -28,7 +28,7 @@ class Other { icon = { Icon(Icons.Default.Map, contentDescription = "A map icon") }, - after = general + after = interfaceAndFonts ) fun register() { PDEPreferences.register( @@ -36,6 +36,7 @@ class Other { key = "other", descriptionKey = "preferences.other", group = other, + noPadding = true, control = { _, _ -> val prefs = LocalPreferences.current val groups = LocalPreferenceGroups.current From 1eccd7a46e0981fd355661e33c2aec1de56bb0d4 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 22 Oct 2025 10:09:27 +0200 Subject: [PATCH 46/50] Refactor preferences window to use Compose UI Replaces the legacy PreferencesFrame with a new Compose-based preferences window using PDESwingWindow and PDETheme. Updates window theming and makes the preferences window resizable. Also fixes background modifier usage in PDETheme. --- app/src/processing/app/Base.java | 10 ++++++---- app/src/processing/app/ui/Preferences.kt | 20 +++++++++++++++++++- app/src/processing/app/ui/theme/Theme.kt | 2 +- app/src/processing/app/ui/theme/Window.kt | 4 ++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 2551a54d64..e3eae12fb8 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -40,6 +40,7 @@ import processing.app.contrib.*; import processing.app.tools.Tool; import processing.app.ui.*; +import processing.app.ui.PreferencesKt; import processing.app.ui.Toolkit; import processing.core.*; import processing.data.StringList; @@ -2190,10 +2191,11 @@ static private Mode findSketchMode(File folder, List modeList) { * Show the Preferences window. */ public void handlePrefs() { - if (preferencesFrame == null) { - preferencesFrame = new PreferencesFrame(this); - } - preferencesFrame.showFrame(); +// if (preferencesFrame == null) { +// preferencesFrame = new PreferencesFrame(this); +// } +// preferencesFrame.showFrame(); + PreferencesKt.show(); } diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index af15caabd7..8a1387ff55 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -36,11 +36,14 @@ import androidx.compose.ui.window.application import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import processing.app.LocalPreferences +import processing.app.ui.PDEPreferences.Companion.preferences import processing.app.ui.preferences.General import processing.app.ui.preferences.Interface import processing.app.ui.preferences.Other import processing.app.ui.theme.LocalLocale +import processing.app.ui.theme.PDESwingWindow import processing.app.ui.theme.PDETheme +import javax.swing.SwingUtilities val LocalPreferenceGroups = compositionLocalOf>> { error("No Preference Groups Set") @@ -175,6 +178,8 @@ class PDEPreferences { } } + + @JvmStatic fun main(args: Array) { application { @@ -264,4 +269,17 @@ data class PDEPreferenceGroup( * The group that comes before this one in the list. */ val after: PDEPreferenceGroup? = null, -) \ No newline at end of file +) + +fun show(){ + SwingUtilities.invokeLater { + PDESwingWindow( + titleKey = "preferences", + fullWindowContent = true + ) { + PDETheme { + preferences() + } + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index 7cc70455f0..9e41227ed1 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -93,7 +93,7 @@ fun PDETheme( colorScheme = if(darkTheme) PDEDarkColor else PDELightColor, typography = PDETypography ){ - Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize()) { + Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) { CompositionLocalProvider( LocalContentColor provides MaterialTheme.colorScheme.onBackground, LocalDensity provides Density(1.25f, 1.25f), diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index 6f49843678..6d05a0885e 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -57,7 +57,7 @@ class PDESwingWindow(titleKey: String = "", fullWindowContent: Boolean = false, if (e.keyCode == KeyEvent.VK_ESCAPE) window.dispose() } }) - isResizable = false + isResizable = true isVisible = true requestFocus() } @@ -81,7 +81,7 @@ private fun PDEWindowContent(window: JFrame, titleKey: String, fullWindowContent } CompositionLocalProvider(LocalWindow provides window) { - ProcessingTheme { + PDETheme { val locale = LocalLocale.current window.title = locale[titleKey] LaunchedEffect(locale) { From 2412c64f79f3c83cb7aafa1331f2911852c526ee Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 22 Oct 2025 10:30:39 +0200 Subject: [PATCH 47/50] Set default window size and clean up window packing Sets the default size of PDESwingWindow to 800x600 and removes redundant window packing and centering logic. Also adds minor layout improvements in Preferences.kt for better UI consistency. --- app/src/processing/app/ui/Preferences.kt | 18 +++++++++++++++--- app/src/processing/app/ui/theme/Window.kt | 6 +----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index 8a1387ff55..61a62a910b 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api @@ -30,9 +32,14 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPlacement +import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import processing.app.LocalPreferences @@ -92,7 +99,8 @@ class PDEPreferences { ) }, - modifier = Modifier.defaultMinSize(minWidth = 200.dp) + modifier = Modifier + .defaultMinSize(minWidth = 200.dp) ) { for (group in sortedGroups) { @@ -112,7 +120,9 @@ class PDEPreferences { } } Box(modifier = Modifier.padding(top = 42.dp)) { - Column(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier + .fillMaxSize() + ) { var query by remember { mutableStateOf("") } val locale = LocalLocale.current LaunchedEffect(query){ @@ -167,7 +177,9 @@ class PDEPreferences { } val preferences = visible[selected] ?: emptyList() - LazyColumn(verticalArrangement = Arrangement.spacedBy(20.dp)) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { items(preferences){ preference -> preference.showControl() } diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index 6d05a0885e..f19da70606 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -43,6 +43,7 @@ val LocalWindow = compositionLocalOf { error("No Window Set") } class PDESwingWindow(titleKey: String = "", fullWindowContent: Boolean = false, content: @Composable BoxScope.() -> Unit): JFrame(){ init{ val window = this + size = java.awt.Dimension(800, 600) defaultCloseOperation = DISPOSE_ON_CLOSE ComposePanel().apply { setContent { @@ -84,11 +85,6 @@ private fun PDEWindowContent(window: JFrame, titleKey: String, fullWindowContent PDETheme { val locale = LocalLocale.current window.title = locale[titleKey] - LaunchedEffect(locale) { - window.pack() - window.setLocationRelativeTo(null) - } - Box(modifier = Modifier.padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp),content = content) } } From 6bc97217435dfea817ba5e736adc8903de410fa5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 22 Oct 2025 11:19:57 +0200 Subject: [PATCH 48/50] Refactor PDESwingWindow to use ComposeWindow Replaces ComposePanel with ComposeWindow in PDESwingWindow for improved integration with Compose UI. Removes manual JFrame setup and simplifies window initialization, packing, and visibility. Updates related code to match new window handling. --- app/src/processing/app/ui/theme/Window.kt | 46 +++++++++++++---------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index f19da70606..a2cc2f012e 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -9,12 +9,14 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel +import androidx.compose.ui.awt.ComposeWindow import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.rememberWindowState import com.formdev.flatlaf.util.SystemInfo +import java.awt.Dimension import java.awt.event.KeyAdapter import java.awt.event.KeyEvent @@ -40,27 +42,23 @@ val LocalWindow = compositionLocalOf { error("No Window Set") } * @param fullWindowContent If true, the content will extend into the title bar area on macOS. * @param content The composable content to be displayed in the window. */ -class PDESwingWindow(titleKey: String = "", fullWindowContent: Boolean = false, content: @Composable BoxScope.() -> Unit): JFrame(){ +// TODO: Add support for onClose callback +// TODO: Window placement +// TODO: Window sizing constraints +class PDESwingWindow( + titleKey: String = "", + fullWindowContent: Boolean = false, + content: @Composable BoxScope.() -> Unit +){ init{ - val window = this - size = java.awt.Dimension(800, 600) - defaultCloseOperation = DISPOSE_ON_CLOSE - ComposePanel().apply { + ComposeWindow().apply { + defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE setContent { - PDEWindowContent(window, titleKey, fullWindowContent, content) + PDEWindowContent(this@apply, titleKey, fullWindowContent, content) } - window.add(this) + pack() + isVisible = true } - background = java.awt.Color.white - setLocationRelativeTo(null) - addKeyListener(object : KeyAdapter() { - override fun keyPressed(e: KeyEvent) { - if (e.keyCode == KeyEvent.VK_ESCAPE) window.dispose() - } - }) - isResizable = true - isVisible = true - requestFocus() } } @@ -74,7 +72,12 @@ class PDESwingWindow(titleKey: String = "", fullWindowContent: Boolean = false, * @param content The composable content to be displayed in the window. */ @Composable -private fun PDEWindowContent(window: JFrame, titleKey: String, fullWindowContent: Boolean = false, content: @Composable BoxScope.() -> Unit){ +private fun PDEWindowContent( + window: JFrame, + titleKey: String, + fullWindowContent: Boolean = false, + content: @Composable BoxScope.() -> Unit +){ val mac = SystemInfo.isMacOS && SystemInfo.isMacFullWindowContentSupported remember { window.rootPane.putClientProperty("apple.awt.fullWindowContent", mac && fullWindowContent) @@ -125,7 +128,12 @@ private fun PDEWindowContent(window: JFrame, titleKey: String, fullWindowContent * */ @Composable -fun PDEComposeWindow(titleKey: String, fullWindowContent: Boolean = false, onClose: () -> Unit = {}, content: @Composable BoxScope.() -> Unit){ +fun PDEComposeWindow( + titleKey: String, + fullWindowContent: Boolean = false, + onClose: () -> Unit = {}, + content: @Composable BoxScope.() -> Unit +){ val windowState = rememberWindowState( size = DpSize.Unspecified, position = WindowPosition(Alignment.Center) From 9ab55475a0d23a164974d62dd60c916177fd3a6f Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 23 Oct 2025 07:07:54 +0200 Subject: [PATCH 49/50] Add documentation for preferences UI components Added KDoc comments to composable functions and data classes in Preferences.kt to improve code clarity and usage guidance. Also removed unnecessary horizontal padding from preference controls in General.kt for a cleaner UI layout. --- app/src/processing/app/ui/Preferences.kt | 26 +++++++++++++++++++ .../processing/app/ui/preferences/General.kt | 7 +---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index 61a62a910b..95796aef77 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -70,6 +70,9 @@ class PDEPreferences { Other.register() } + /** + * Composable function to display the preferences UI. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun preferences(){ @@ -218,6 +221,23 @@ class PDEPreferences { } } +/** + * Data class representing a single preference in the preferences system. + * + * Usage: + * ``` + * PDEPreferences.register( + * PDEPreference( + * key = "preference.key", + * descriptionKey = "preference.description", + * group = somePreferenceGroup, + * control = { preference, updatePreference -> + * // Composable UI to modify the preference + * } + * ) + * ) + * ``` + */ data class PDEPreference( /** * The key in the preferences file used to store this preference. @@ -243,6 +263,9 @@ data class PDEPreference( val noPadding: Boolean = false, ) +/** + * Composable function to display the preference's description and control. + */ @Composable private fun PDEPreference.showControl() { val locale = LocalLocale.current @@ -268,6 +291,9 @@ private fun PDEPreference.showControl() { } +/** + * Data class representing a group of preferences. + */ data class PDEPreferenceGroup( /** * The name of this group. diff --git a/app/src/processing/app/ui/preferences/General.kt b/app/src/processing/app/ui/preferences/General.kt index 18921a7474..5f56187f46 100644 --- a/app/src/processing/app/ui/preferences/General.kt +++ b/app/src/processing/app/ui/preferences/General.kt @@ -41,7 +41,6 @@ class General { horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp) ) { TextField( value = preference ?: "", @@ -66,9 +65,7 @@ class General { descriptionKey = "preferences.sketch_naming", group = general, control = { preference, updatePreference -> - Row( - modifier = Modifier.padding(horizontal = 20.dp) - ) { + Row{ for (option in if(Preferences.isInitialized()) SketchName.getOptions() else arrayOf( "timestamp", "untitled", @@ -97,7 +94,6 @@ class General { control = { preference, updatePreference -> Switch( checked = preference.toBoolean(), - modifier = Modifier.padding(horizontal = 20.dp), onCheckedChange = { updatePreference(it.toString()) } @@ -113,7 +109,6 @@ class General { control = { preference, updatePreference -> Switch( checked = preference.toBoolean(), - modifier = Modifier.padding(horizontal = 20.dp), onCheckedChange = { updatePreference(it.toString()) } From d8892b61475299522b8db37ab5b38fba8e818a65 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 23 Oct 2025 08:22:35 +0200 Subject: [PATCH 50/50] Add window sizing options to PDESwingWindow and PDEComposeWindow Introduces parameters for setting window size, minimum size, and maximum size in PDESwingWindow and PDEComposeWindow. Updates Preferences to specify window size for preferences dialog. Refactors window content composable signatures and adds utility for DpSize to Dimension conversion. Includes a main function example for PDEComposeWindow. --- app/src/processing/app/ui/Preferences.kt | 4 +- app/src/processing/app/ui/theme/Window.kt | 81 ++++++++++++++++++++--- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt index 95796aef77..7fd9f56350 100644 --- a/app/src/processing/app/ui/Preferences.kt +++ b/app/src/processing/app/ui/Preferences.kt @@ -50,6 +50,7 @@ import processing.app.ui.preferences.Other import processing.app.ui.theme.LocalLocale import processing.app.ui.theme.PDESwingWindow import processing.app.ui.theme.PDETheme +import java.awt.Dimension import javax.swing.SwingUtilities val LocalPreferenceGroups = compositionLocalOf>> { @@ -313,7 +314,8 @@ fun show(){ SwingUtilities.invokeLater { PDESwingWindow( titleKey = "preferences", - fullWindowContent = true + fullWindowContent = true, + size = Dimension(800, 600) ) { PDETheme { preferences() diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt index a2cc2f012e..5104f247bd 100644 --- a/app/src/processing/app/ui/theme/Window.kt +++ b/app/src/processing/app/ui/theme/Window.kt @@ -1,6 +1,8 @@ package processing.app.ui.theme +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -10,10 +12,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.ComposePanel import androidx.compose.ui.awt.ComposeWindow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import com.formdev.flatlaf.util.SystemInfo import java.awt.Dimension @@ -21,6 +26,7 @@ import java.awt.Dimension import java.awt.event.KeyAdapter import java.awt.event.KeyEvent import javax.swing.JFrame +import javax.swing.UIManager val LocalWindow = compositionLocalOf { error("No Window Set") } @@ -39,24 +45,38 @@ val LocalWindow = compositionLocalOf { error("No Window Set") } * ``` * * @param titleKey The key for the window title, which will be localized. + * @param size The desired size of the window. If null, the window will use its default size. + * @param minSize The minimum size of the window. If null, no minimum size is set. + * @param maxSize The maximum size of the window. If null, no maximum size is set. * @param fullWindowContent If true, the content will extend into the title bar area on macOS. * @param content The composable content to be displayed in the window. */ // TODO: Add support for onClose callback -// TODO: Window placement -// TODO: Window sizing constraints class PDESwingWindow( titleKey: String = "", + size: Dimension? = null, + minSize: Dimension? = null, + maxSize: Dimension? = null, fullWindowContent: Boolean = false, - content: @Composable BoxScope.() -> Unit + content: @Composable () -> Unit ){ init{ ComposeWindow().apply { + val window = this defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE + size?.let { + window.size = it + } + minSize?.let { + window.minimumSize = it + } + maxSize?.let { + window.maximumSize = it + } + setLocationRelativeTo(null) setContent { - PDEWindowContent(this@apply, titleKey, fullWindowContent, content) + PDEWindowContent(window, titleKey, fullWindowContent, content) } - pack() isVisible = true } } @@ -73,10 +93,10 @@ class PDESwingWindow( */ @Composable private fun PDEWindowContent( - window: JFrame, + window: ComposeWindow, titleKey: String, fullWindowContent: Boolean = false, - content: @Composable BoxScope.() -> Unit + content: @Composable () -> Unit ){ val mac = SystemInfo.isMacOS && SystemInfo.isMacFullWindowContentSupported remember { @@ -85,10 +105,10 @@ private fun PDEWindowContent( } CompositionLocalProvider(LocalWindow provides window) { - PDETheme { + PDETheme{ val locale = LocalLocale.current window.title = locale[titleKey] - Box(modifier = Modifier.padding(top = if (mac && !fullWindowContent) 22.dp else 0.dp),content = content) + content() } } } @@ -119,6 +139,10 @@ private fun PDEWindowContent( * ``` * * @param titleKey The key for the window title, which will be localized. + * @param size The desired size of the window. Defaults to unspecified size which means the window will be + * fullscreen if it contains any of [fillMaxWidth]/[fillMaxSize]/[fillMaxHeight] etc. + * @param minSize The minimum size of the window. Defaults to unspecified size which means no minimum size is set. + * @param maxSize The maximum size of the window. Defaults to unspecified size which means no maximum size is set. * @param fullWindowContent If true, the content will extend into the title bar area on * macOS. * @param onClose A lambda function to be called when the window is requested to close. @@ -130,15 +154,50 @@ private fun PDEWindowContent( @Composable fun PDEComposeWindow( titleKey: String, + size: DpSize = DpSize.Unspecified, + minSize: DpSize = DpSize.Unspecified, + maxSize: DpSize = DpSize.Unspecified, fullWindowContent: Boolean = false, onClose: () -> Unit = {}, - content: @Composable BoxScope.() -> Unit + content: @Composable () -> Unit ){ val windowState = rememberWindowState( - size = DpSize.Unspecified, + size = size, position = WindowPosition(Alignment.Center) ) Window(onCloseRequest = onClose, state = windowState, title = "") { + remember { + window.minimumSize = minSize.toDimension() + window.maximumSize = maxSize.toDimension() + } PDEWindowContent(window, titleKey, fullWindowContent, content) } +} + +fun DpSize.toDimension(): Dimension? { + if(this == DpSize.Unspecified) { return null } + + return Dimension( + this.width.value.toInt(), + this.height.value.toInt() + ) +} + +fun main(){ + application { + PDEComposeWindow( + onClose = ::exitApplication, + titleKey = "window.title", + size = DpSize(800.dp, 600.dp), + ){ + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + contentAlignment = Alignment.Center + ) { + Text("Hello, World!") + } + } + } } \ No newline at end of file