From e6d6a93fd26583677089963c3f802b1d0455f5dd Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 22:25:30 +0100 Subject: [PATCH 001/160] Show Welcome to Beta Screen --- app/ant/processing/app/ui/WelcomeToBeta.java | 11 +++++ app/src/processing/app/UpdateCheck.java | 4 ++ app/src/processing/app/ui/WelcomeToBeta.kt | 46 ++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 app/ant/processing/app/ui/WelcomeToBeta.java create mode 100644 app/src/processing/app/ui/WelcomeToBeta.kt diff --git a/app/ant/processing/app/ui/WelcomeToBeta.java b/app/ant/processing/app/ui/WelcomeToBeta.java new file mode 100644 index 000000000..3127e4adc --- /dev/null +++ b/app/ant/processing/app/ui/WelcomeToBeta.java @@ -0,0 +1,11 @@ +package processing.app.ui; + + +// Stub class for backwards compatibility with the ant-build system +// This class is not used in the Gradle build system +// The actual implementation is in src/.../Schema.kt +public class WelcomeToBeta { + public static void showWelcomeToBeta(){ + + } +} diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 40ffe24c0..0e32a6f69 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -31,6 +31,7 @@ import java.util.Random; import javax.swing.JOptionPane; +import processing.app.ui.WelcomeToBeta; import processing.core.PApplet; @@ -134,6 +135,9 @@ public class UpdateCheck { // offerToUpdateContributions = !promptToVisitDownloadPage(); promptToVisitDownloadPage(); } + if(latest < Base.getRevision()){ + WelcomeToBeta.showWelcomeToBeta(); + } /* if (offerToUpdateContributions) { diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt new file mode 100644 index 000000000..5cb3ea6f1 --- /dev/null +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -0,0 +1,46 @@ +package processing.app.ui + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.awt.ComposePanel +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import javax.swing.JFrame +import javax.swing.SwingUtilities + + +class WelcomeToBeta { + companion object{ + @JvmStatic + fun showWelcomeToBeta() { + SwingUtilities.invokeLater { + JFrame("New Window Title").apply { + defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE + contentPane.add(ComposePanel().apply { + setContent { + welcomeToBeta() + } + }) + setSize(400, 300) + setLocationRelativeTo(null) + isVisible = true + } + } + } + + @Composable + fun welcomeToBeta() { + Text("Welcome to the Beta version of Processing!") + } + + @JvmStatic + fun main(args: Array) { + application { + Window(onCloseRequest = ::exitApplication) { + welcomeToBeta() + } + } + } + } +} + From 4714a8b83b2b33a39c017bccce6ede37e3fe1f92 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 10:35:39 +0100 Subject: [PATCH 002/160] Welcome to Beta screen: Initial layout --- app/build.gradle.kts | 2 + app/src/processing/app/ui/WelcomeToBeta.kt | 80 ++++++++++++++++++++-- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6a0290694..05414e14d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,8 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download +// TODO: Update to 2.10.20 and add hot-reloading: https://github.com/JetBrains/compose-hot-reload + plugins{ id("java") kotlin("jvm") version libs.versions.kotlin diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index 5cb3ea6f1..97c4a453a 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -1,27 +1,53 @@ package processing.app.ui +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +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 import androidx.compose.ui.awt.ComposePanel +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.text.font.FontStyle +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 javax.swing.JFrame import javax.swing.SwingUtilities class WelcomeToBeta { companion object{ + val windowSize = Pair(400, 200) + val windowTitle = "Welcome to Beta" + val title = "Welcome to the Processing Beta" + val message = """Thank you for trying out the new version of Processing. We’re very grateful! + +Please report any bugs on the forums.""" + val buttonText = "Thank you" + + @JvmStatic fun showWelcomeToBeta() { SwingUtilities.invokeLater { - JFrame("New Window Title").apply { + JFrame(windowTitle).apply { defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE contentPane.add(ComposePanel().apply { setContent { welcomeToBeta() } }) - setSize(400, 300) +// setSize(windowSize.first, windowSize.second) setLocationRelativeTo(null) isVisible = true } @@ -30,14 +56,58 @@ class WelcomeToBeta { @Composable fun welcomeToBeta() { - Text("Welcome to the Beta version of Processing!") + // TODO: Add fonts and colors + + Row( + modifier = Modifier + .padding(20.dp, 10.dp) + .size(windowSize.first.dp, windowSize.second.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) + { + // TODO: Add the Processing logo svg here + Box(modifier = Modifier + .align(Alignment.CenterVertically) + .size(100.dp, 100.dp) + .background(Color.Blue) + ) + Column(modifier = Modifier + .fillMaxHeight(), + verticalArrangement = Arrangement.spacedBy(20.dp, alignment = Alignment.CenterVertically) + ) { + Text(title, fontSize = 17.sp, fontWeight = FontWeight.SemiBold) + Text(message, fontSize = 13.sp) + Row { + Spacer(modifier = Modifier.weight(1f)) + // TODO Add button shadow and make interactive + Box( + modifier = Modifier + .background(Color.Blue) + .padding(10.dp) + .sizeIn(minWidth = 100.dp) + + , + contentAlignment = Alignment.Center + ) { + Text(buttonText, color = Color.White) + } + } + } + } } @JvmStatic fun main(args: Array) { application { - Window(onCloseRequest = ::exitApplication) { - welcomeToBeta() + val windowState = rememberWindowState( + size = DpSize.Unspecified, + position = WindowPosition(Alignment.Center) + ) + Window(onCloseRequest = ::exitApplication, state = windowState, title = windowTitle) { + Surface(color = Color.White) { + welcomeToBeta() + } + } } } From 5ce873d21bbdb6e8a54ac867395c9d2466efc803 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 13:09:39 +0100 Subject: [PATCH 003/160] Welcome to Beta screen: SVG Logo --- app/src/main/resources/logo.svg | 5 +++ app/src/processing/app/ui/WelcomeToBeta.kt | 47 ++++++++++++---------- 2 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 app/src/main/resources/logo.svg diff --git a/app/src/main/resources/logo.svg b/app/src/main/resources/logo.svg new file mode 100644 index 000000000..aa04fcb29 --- /dev/null +++ b/app/src/main/resources/logo.svg @@ -0,0 +1,5 @@ + + + + + \ 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 97c4a453a..79635be18 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -1,18 +1,16 @@ package processing.app.ui +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme 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 import androidx.compose.ui.awt.ComposePanel -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.text.font.FontStyle +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 @@ -21,6 +19,7 @@ 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 java.awt.Dimension import javax.swing.JFrame import javax.swing.SwingUtilities @@ -28,7 +27,7 @@ import javax.swing.SwingUtilities class WelcomeToBeta { companion object{ - val windowSize = Pair(400, 200) + val windowSize = Dimension(400, 200) val windowTitle = "Welcome to Beta" val title = "Welcome to the Processing Beta" val message = """Thank you for trying out the new version of Processing. We’re very grateful! @@ -46,8 +45,8 @@ Please report any bugs on the forums.""" setContent { welcomeToBeta() } + size = windowSize }) -// setSize(windowSize.first, windowSize.second) setLocationRelativeTo(null) isVisible = true } @@ -61,22 +60,30 @@ Please report any bugs on the forums.""" Row( modifier = Modifier .padding(20.dp, 10.dp) - .size(windowSize.first.dp, windowSize.second.dp), + .size(windowSize.width.dp, windowSize.height.dp), horizontalArrangement = Arrangement.spacedBy(20.dp) - ) - { - // TODO: Add the Processing logo svg here - Box(modifier = Modifier - .align(Alignment.CenterVertically) - .size(100.dp, 100.dp) - .background(Color.Blue) + ){ + Image( + painter = painterResource("logo.svg"), + contentDescription = "Processing Logo", + modifier = Modifier + .align(Alignment.CenterVertically) + .size(100.dp, 100.dp) ) - Column(modifier = Modifier - .fillMaxHeight(), + Column( + modifier = Modifier + .fillMaxHeight(), verticalArrangement = Arrangement.spacedBy(20.dp, alignment = Alignment.CenterVertically) ) { - Text(title, fontSize = 17.sp, fontWeight = FontWeight.SemiBold) - Text(message, fontSize = 13.sp) + Text( + title, + fontSize = 17.sp, + fontWeight = FontWeight.SemiBold + ) + Text( + message, + fontSize = 13.sp + ) Row { Spacer(modifier = Modifier.weight(1f)) // TODO Add button shadow and make interactive @@ -84,9 +91,7 @@ Please report any bugs on the forums.""" modifier = Modifier .background(Color.Blue) .padding(10.dp) - .sizeIn(minWidth = 100.dp) - - , + .sizeIn(minWidth = 100.dp), contentAlignment = Alignment.Center ) { Text(buttonText, color = Color.White) From a3635bc74851dd5d15b219acb0763fe91d77a3e2 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 14:45:47 +0100 Subject: [PATCH 004/160] Welcome to Beta screen: Animations, Interaction --- app/src/processing/app/ui/WelcomeToBeta.kt | 97 ++++++++++++++++++---- 1 file changed, 80 insertions(+), 17 deletions(-) diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index 79635be18..773e81be1 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -1,15 +1,22 @@ 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.Surface 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.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 @@ -19,8 +26,11 @@ 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 java.awt.Cursor import java.awt.Dimension - +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent import javax.swing.JFrame import javax.swing.SwingUtilities @@ -35,32 +45,47 @@ class WelcomeToBeta { Please report any bugs on the forums.""" val buttonText = "Thank you" - @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 { - setContent { - welcomeToBeta() - } size = windowSize + setContent { + 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() } } } @Composable - fun welcomeToBeta() { + fun welcomeToBeta(close: () -> Unit = {}) { // TODO: Add fonts and colors Row( modifier = Modifier .padding(20.dp, 10.dp) - .size(windowSize.width.dp, windowSize.height.dp), + .size(windowSize.width.dp, windowSize.height.dp) + , horizontalArrangement = Arrangement.spacedBy(20.dp) ){ Image( @@ -86,20 +111,55 @@ Please report any bugs on the forums.""" ) Row { Spacer(modifier = Modifier.weight(1f)) - // TODO Add button shadow and make interactive - Box( - modifier = Modifier - .background(Color.Blue) - .padding(10.dp) - .sizeIn(minWidth = 100.dp), - contentAlignment = Alignment.Center - ) { + PDEButton(onClick = { + close() + }) { Text(buttonText, color = Color.White) } } } } } + @OptIn(ExperimentalComposeUiApi::class) + @Composable + fun PDEButton(onClick: () -> Unit, content: @Composable BoxScope.() -> 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) Color.Black else Color.Blue) + + Box(modifier = Modifier.padding(end = 5.dp, top = 5.dp)) { + Box( + modifier = Modifier + .offset((-offset).dp, (offset).dp) + .background(Color.Gray) + .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) { @@ -108,9 +168,12 @@ Please report any bugs on the forums.""" size = DpSize.Unspecified, position = WindowPosition(Alignment.Center) ) + Window(onCloseRequest = ::exitApplication, state = windowState, title = windowTitle) { Surface(color = Color.White) { - welcomeToBeta() + welcomeToBeta{ + exitApplication() + } } } From ed49b65c48b39e1322584910f5d7bcadcedcc160 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 20:17:55 +0100 Subject: [PATCH 005/160] Welcome to Beta screen: Colors, Typhography, Locale --- app/build.gradle.kts | 3 + app/src/processing/app/ui/WelcomeToBeta.kt | 66 ++++++++++------- app/src/processing/app/ui/theme/Locale.kt | 31 ++++++++ app/src/processing/app/ui/theme/Theme.kt | 71 +++++++++++++++++++ app/src/processing/app/ui/theme/Typography.kt | 38 ++++++++++ build/shared/lib/languages/PDE.properties | 7 ++ build/shared/lib/languages/PDE_nl.properties | 7 ++ 7 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 app/src/processing/app/ui/theme/Locale.kt create mode 100644 app/src/processing/app/ui/theme/Theme.kt create mode 100644 app/src/processing/app/ui/theme/Typography.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 05414e14d..8dd6e88f0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,6 +38,9 @@ sourceSets{ kotlin{ srcDirs("src") } + resources{ + srcDirs("resources", listOf("languages", "fonts", "theme").map { "../build/shared/lib/$it" }) + } } } diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index 773e81be1..c057deba9 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -5,6 +5,8 @@ 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.Surface import androidx.compose.material.Text import androidx.compose.runtime.* @@ -27,10 +29,16 @@ 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 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 @@ -38,12 +46,7 @@ import javax.swing.SwingUtilities class WelcomeToBeta { companion object{ val windowSize = Dimension(400, 200) - val windowTitle = "Welcome to Beta" - val title = "Welcome to the Processing Beta" - val message = """Thank you for trying out the new version of Processing. We’re very grateful! - -Please report any bugs on the forums.""" - val buttonText = "Thank you" + val windowTitle = Locale()["beta.window.title"] @JvmStatic fun showWelcomeToBeta() { @@ -57,8 +60,10 @@ Please report any bugs on the forums.""" contentPane.add(ComposePanel().apply { size = windowSize setContent { - Box(modifier = Modifier.padding(top = if(mac) 22.dp else 0.dp)) { - welcomeToBeta(close) + ProcessingTheme { + Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) { + welcomeToBeta(close) + } } } }) @@ -79,14 +84,12 @@ Please report any bugs on the forums.""" @Composable fun welcomeToBeta(close: () -> Unit = {}) { - // TODO: Add fonts and colors - Row( modifier = Modifier .padding(20.dp, 10.dp) - .size(windowSize.width.dp, windowSize.height.dp) - , - horizontalArrangement = Arrangement.spacedBy(20.dp) + .size(windowSize.width.dp, windowSize.height.dp), + horizontalArrangement = Arrangement + .spacedBy(20.dp) ){ Image( painter = painterResource("logo.svg"), @@ -97,24 +100,30 @@ Please report any bugs on the forums.""" ) Column( modifier = Modifier - .fillMaxHeight(), - verticalArrangement = Arrangement.spacedBy(20.dp, alignment = Alignment.CenterVertically) + .fillMaxHeight(), + verticalArrangement = Arrangement + .spacedBy( + MaterialTheme.typography.subtitle1.lineHeight.value.dp, + alignment = Alignment.CenterVertically + ) ) { + val locale = LocalLocale.current Text( - title, - fontSize = 17.sp, - fontWeight = FontWeight.SemiBold + text = locale["beta.title"], + style = MaterialTheme.typography.subtitle1, ) Text( - message, - fontSize = 13.sp + text = locale["beta.message"] ) Row { Spacer(modifier = Modifier.weight(1f)) PDEButton(onClick = { close() }) { - Text(buttonText, color = Color.White) + Text( + text = locale["beta.button"], + color = Color.White + ) } } } @@ -123,16 +132,18 @@ Please report any bugs on the forums.""" @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) Color.Black else Color.Blue) + 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(Color.Gray) + .background(theme.getColor("toolbar.button.pressed.field")) .matchParentSize() ) Box( @@ -170,12 +181,13 @@ Please report any bugs on the forums.""" ) Window(onCloseRequest = ::exitApplication, state = windowState, title = windowTitle) { - Surface(color = Color.White) { - welcomeToBeta{ - exitApplication() + ProcessingTheme { + Surface(color = colors.background) { + welcomeToBeta { + exitApplication() + } } } - } } } diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt new file mode 100644 index 000000000..97940a085 --- /dev/null +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -0,0 +1,31 @@ +package processing.app.ui.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import java.io.InputStream +import java.util.* + +class Locale : Properties() { + 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()) + } + + @Deprecated("Use get instead", ReplaceWith("get(key)")) + override fun getProperty(key: String?): String { + return super.getProperty(key) + } + operator fun get(key: String): String = getProperty(key) +} +val LocalLocale = compositionLocalOf { Locale() } +@Composable +fun LocaleProvider(content: @Composable () -> Unit) { + val locale = Locale() + // TODO: Listen for languages changes + CompositionLocalProvider(LocalLocale provides locale) { + content() + } +} \ 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 new file mode 100644 index 000000000..bd2524d64 --- /dev/null +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -0,0 +1,71 @@ +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 +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.graphics.Color +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") } + +@Composable +fun ProcessingTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable() () -> Unit +) { + + val theme = 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.pressed.field"), + onSecondary = theme.getColor("toolbar.button.pressed.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, + 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/Typography.kt b/app/src/processing/app/ui/theme/Typography.kt new file mode 100644 index 000000000..5d87c490e --- /dev/null +++ b/app/src/processing/app/ui/theme/Typography.kt @@ -0,0 +1,38 @@ +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 +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.platform.Font +import androidx.compose.ui.unit.sp + +val processingFont = FontFamily( + Font( + resource = "ProcessingSans-Regular.ttf", + weight = FontWeight.Normal, + style = FontStyle.Normal + ), + Font( + resource = "ProcessingSans-Bold.ttf", + weight = FontWeight.Bold, + style = FontStyle.Normal + ) +) + +val Typography = Typography( + body1 = TextStyle( + fontFamily = processingFont, + fontWeight = FontWeight.Normal, + fontSize = 13.sp, + lineHeight = 16.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/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index 02f98473e..be680c159 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -614,6 +614,13 @@ update_check.updates_available.core = A new version of Processing is available,\ update_check.updates_available.contributions = There are updates available for some of the installed contributions,\nwould you like to open the the Contribution Manager now? +# --------------------------------------- +# Beta +beta.window.title = Welcome to Beta +beta.title = Welcome to the Processing Beta +beta.message = Thank you for trying out the new version of Processing. We?re very grateful!\n\nPlease report any bugs on the forums. +beta.button = Got it! + # --------------------------------------- # Color Chooser diff --git a/build/shared/lib/languages/PDE_nl.properties b/build/shared/lib/languages/PDE_nl.properties index 9b9527517..f1ac08b5e 100644 --- a/build/shared/lib/languages/PDE_nl.properties +++ b/build/shared/lib/languages/PDE_nl.properties @@ -315,6 +315,13 @@ update_check = Update update_check.updates_available.core = Een nieuwe versie van Processing is beschikbaar,\nwilt u de Processing download pagina bezoeken? update_check.updates_available.contributions = Er zijn updates beschikbaar voor sommige van de door u geïnstalleerde bijdragen,\nwilt u nu de Bijdragen Manager openen? +# --------------------------------------- +# Beta +beta.window.title = Welkom bij Beta +beta.title = Welkom bij de Processing Beta +beta.message = Bedankt dat je de nieuwe versie van Processing uitprobeert. We zijn je zeer dankbaar!\n\nMeld eventuele bugs alsjeblieft op de forums. +beta.button = Okee! + # --------------------------------------- # Color Chooser From 67da3ac20573f9806e4a64b80e2e1ce88c5d288a Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 20:53:15 +0100 Subject: [PATCH 006/160] Welcome to Beta screen: Preferences --- .../app/{contrib/ui => }/Preferences.kt | 34 ++++++------ .../app/contrib/ui/ContributionManager.kt | 14 +---- app/src/processing/app/ui/WelcomeToBeta.kt | 2 +- app/src/processing/app/ui/theme/Locale.kt | 17 ++++-- app/src/processing/app/ui/theme/Theme.kt | 52 ++++++++++--------- 5 files changed, 63 insertions(+), 56 deletions(-) rename app/src/processing/app/{contrib/ui => }/Preferences.kt (69%) diff --git a/app/src/processing/app/contrib/ui/Preferences.kt b/app/src/processing/app/Preferences.kt similarity index 69% rename from app/src/processing/app/contrib/ui/Preferences.kt rename to app/src/processing/app/Preferences.kt index b344e1cb7..c5645c9bb 100644 --- a/app/src/processing/app/contrib/ui/Preferences.kt +++ b/app/src/processing/app/Preferences.kt @@ -1,11 +1,10 @@ -package processing.app.contrib.ui +package processing.app import androidx.compose.runtime.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import processing.app.Base -import processing.app.Platform import java.io.File +import java.io.InputStream import java.nio.file.* import java.util.Properties @@ -13,10 +12,13 @@ import java.util.Properties const val PREFERENCES_FILE_NAME = "preferences.txt" const val DEFAULTS_FILE_NAME = "defaults.txt" +fun PlatformStart(){ + Platform.inst ?: Platform.init() +} @Composable fun loadPreferences(): Properties{ - Platform.init() + PlatformStart() val settingsFolder = Platform.getSettingsFolder() val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME) @@ -24,20 +26,12 @@ fun loadPreferences(): Properties{ if(!preferencesFile.exists()){ preferencesFile.createNewFile() } - val watched = watchFile(preferencesFile) + watchFile(preferencesFile) - val preferences by remember { - mutableStateOf(Properties()) + return Properties().apply { + load(ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME) ?: InputStream.nullInputStream()) + load(preferencesFile.inputStream()) } - - LaunchedEffect(watched){ - val defaults = Base::class.java.getResourceAsStream("/lib/${DEFAULTS_FILE_NAME}") ?: return@LaunchedEffect - - preferences.load(defaults) - preferences.load(preferencesFile.inputStream()) - } - - return preferences } @Composable @@ -68,4 +62,12 @@ 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 a057e76df..2ad472159 100644 --- a/app/src/processing/app/contrib/ui/ContributionManager.kt +++ b/app/src/processing/app/contrib/ui/ContributionManager.kt @@ -12,8 +12,6 @@ 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.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.font.FontWeight @@ -25,6 +23,7 @@ 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 @@ -33,16 +32,7 @@ import kotlin.io.path.* fun main() = application { - val active = remember { mutableStateOf(true) } - if(!active.value){ - Window(onCloseRequest = ::exitApplication) { - - } - return@application - } - Window( - onCloseRequest = { active.value = false }, - ) { + Window(onCloseRequest = ::exitApplication) { contributionsManager() } } diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index c057deba9..7c75bb803 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -122,7 +122,7 @@ class WelcomeToBeta { }) { Text( text = locale["beta.button"], - color = Color.White + color = colors.onPrimary ) } } diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 97940a085..3516b28a3 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -3,15 +3,21 @@ 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.Platform +import processing.app.PlatformStart +import processing.app.watchFile +import java.io.File import java.io.InputStream import java.util.* -class Locale : Properties() { +class Locale(language: String = "") : Properties() { 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()) } @Deprecated("Use get instead", ReplaceWith("get(key)")) @@ -23,8 +29,13 @@ class Locale : Properties() { val LocalLocale = compositionLocalOf { Locale() } @Composable fun LocaleProvider(content: @Composable () -> Unit) { - val locale = Locale() - // TODO: Listen for languages changes + PlatformStart() + + val settingsFolder = Platform.getSettingsFolder() + val languageFile = File(settingsFolder, "language.txt") + watchFile(languageFile) + + val locale = Locale(languageFile.readText().substring(0, 2)) CompositionLocalProvider(LocalLocale provides locale) { content() } diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt index bd2524d64..735d8e5b2 100644 --- a/app/src/processing/app/ui/theme/Theme.kt +++ b/app/src/processing/app/ui/theme/Theme.kt @@ -7,6 +7,8 @@ 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 @@ -28,31 +30,33 @@ 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") + ) - val theme = 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.pressed.field"), - onSecondary = theme.getColor("toolbar.button.pressed.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, - typography = Typography, - content = content - ) + CompositionLocalProvider(LocalTheme provides theme) { + LocaleProvider { + MaterialTheme( + colors = colors, + typography = Typography, + content = content + ) + } } } } From 00c57607d1f5750361bc9dd0af3b95145b13f883 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 22:47:58 +0100 Subject: [PATCH 007/160] Logging cleanup --- app/{src => ant}/processing/app/Messages.java | 0 app/src/processing/app/Base.java | 10 +- app/src/processing/app/Library.java | 23 +- app/src/processing/app/Messages.kt | 282 ++++++++++++++++++ app/src/processing/app/exec/StreamPump.java | 4 +- .../app/syntax/im/InputMethodSupport.java | 8 +- app/src/processing/app/ui/EditorConsole.java | 4 +- .../processing/mode/java/CompletionPanel.java | 2 +- .../processing/mode/java/PreprocService.java | 14 +- .../processing/mode/java/runner/Runner.java | 12 +- 10 files changed, 320 insertions(+), 39 deletions(-) rename app/{src => ant}/processing/app/Messages.java (100%) create mode 100644 app/src/processing/app/Messages.kt diff --git a/app/src/processing/app/Messages.java b/app/ant/processing/app/Messages.java similarity index 100% rename from app/src/processing/app/Messages.java rename to app/ant/processing/app/Messages.java diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index a5b3ac7c0..4690c6946 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -563,14 +563,12 @@ public class Base { cl.downloadAvailableList(this, new ContribProgress(null)); long t9 = System.currentTimeMillis(); - if (DEBUG) { - System.out.println("core modes: " + (t2b-t2) + - ", contrib modes: " + (t2c-t2b) + - ", contrib ex: " + (t2c-t2b)); - System.out.println("base took " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + + Messages.log("core modes: " + (t2b-t2) + + ", contrib modes: " + (t2c-t2b) + + ", contrib ex: " + (t2c-t2b)); + Messages.log("base took " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4) + " t6-t5=" + (t6-t5) + " " + (t7-t6) + " handleNew=" + (t8-t7) + " " + (t9-t8) + " ms"); - } } diff --git a/app/src/processing/app/Library.java b/app/src/processing/app/Library.java index dc8269eeb..e92ee8496 100644 --- a/app/src/processing/app/Library.java +++ b/app/src/processing/app/Library.java @@ -330,6 +330,7 @@ public class Library extends LocalContribution { * imports to specific libraries. * @param importToLibraryTable mapping from package names to Library objects */ + static boolean instruced = false; // public void addPackageList(HashMap importToLibraryTable) { public void addPackageList(Map> importToLibraryTable) { // PApplet.println(packages); @@ -342,18 +343,20 @@ public class Library extends LocalContribution { libraries = new ArrayList<>(); importToLibraryTable.put(pkg, libraries); } else { - if (Base.DEBUG) { - System.err.println("The library found in"); - System.err.println(getPath()); - System.err.println("conflicts with"); + if(!instruced) { + instruced = true; + Messages.err("The library found in"); + Messages.err(getPath()); + Messages.err("conflicts with"); for (Library library : libraries) { - System.err.println(library.getPath()); + Messages.err(library.getPath()); } - System.err.println("which already define(s) the package " + pkg); - System.err.println("If you have a line in your sketch that reads"); - System.err.println("import " + pkg + ".*;"); - System.err.println("Then you'll need to first remove one of those libraries."); - System.err.println(); + Messages.err("which already define(s) the package " + pkg); + Messages.err("If you have a line in your sketch that reads"); + Messages.err("import " + pkg + ".*;"); + Messages.err("Then you'll need to first remove one of those libraries."); + }else{ + Messages.err("\tPackage ("+pkg+")\t conflict found in [" + name + "] with libraries: " + libraries.stream().map(Library::getName).reduce((a, b) -> a + ", " + b).orElse("")); } } libraries.add(this); diff --git a/app/src/processing/app/Messages.kt b/app/src/processing/app/Messages.kt new file mode 100644 index 000000000..cae54e6e9 --- /dev/null +++ b/app/src/processing/app/Messages.kt @@ -0,0 +1,282 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* + Part of the Processing project - http://processing.org + + Copyright (c) 2015 The Processing Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +package processing.app + +import processing.app.ui.Toolkit +import java.awt.EventQueue +import java.awt.Frame +import java.io.PrintWriter +import java.io.StringWriter +import javax.swing.JFrame +import javax.swing.JOptionPane + +class Messages { + companion object { + /** + * "No cookie for you" type messages. Nothing fatal or all that + * much of a bummer, but something to notify the user about. + */ + @JvmStatic + fun showMessage(title: String = "Message", message: String) { + if (Base.isCommandLine()) { + println("$title: $message") + } else { + JOptionPane.showMessageDialog( + Frame(), message, title, + JOptionPane.INFORMATION_MESSAGE + ) + } + } + + + /** + * Non-fatal error message with optional stack trace side dish. + */ + /** + * Non-fatal error message. + */ + @JvmStatic + @JvmOverloads + fun showWarning(title: String = "Warning", message: String, e: Throwable? = null) { + if (Base.isCommandLine()) { + println("$title: $message") + } else { + JOptionPane.showMessageDialog( + Frame(), message, title, + JOptionPane.WARNING_MESSAGE + ) + } + e?.printStackTrace() + } + + /** + * Non-fatal error message with two levels of formatting. + * Unlike the others, this is non-blocking and will run later on the EDT. + */ + @JvmStatic + fun showWarningTiered( + title: String, + primary: String, secondary: String, + e: Throwable? + ) { + if (Base.isCommandLine()) { + // TODO All these messages need to be handled differently for + // proper parsing on the command line. Many have \n in them. + println("$title: $primary\n$secondary") + } else { + EventQueue.invokeLater { + JOptionPane.showMessageDialog( + JFrame(), + Toolkit.formatMessage(primary, secondary), + title, JOptionPane.WARNING_MESSAGE + ) + } + } + e?.printStackTrace() + } + + + /** + * Show an error message that's actually fatal to the program. + * This is an error that can't be recovered. Use showWarning() + * for errors that allow P5 to continue running. + */ + @JvmStatic + fun showError(title: String = "Error", message: String, e: Throwable?) { + if (Base.isCommandLine()) { + System.err.println("$title: $message") + } else { + JOptionPane.showMessageDialog( + Frame(), message, title, + JOptionPane.ERROR_MESSAGE + ) + } + e?.printStackTrace() + System.exit(1) + } + + + /** + * Warning window that includes the stack trace. + */ + @JvmStatic + fun showTrace( + title: String?, + message: String, + t: Throwable?, + fatal: Boolean + ) { + val title = title ?: if (fatal) "Error" else "Warning" + + if (Base.isCommandLine()) { + System.err.println("$title: $message") + t?.printStackTrace() + } else { + val sw = StringWriter() + t!!.printStackTrace(PrintWriter(sw)) + + JOptionPane.showMessageDialog( + Frame(), // first
clears to the next line + // second
is a shorter height blank space before the trace + Toolkit.formatMessage("$message

$sw
"), + title, + if (fatal) JOptionPane.ERROR_MESSAGE else JOptionPane.WARNING_MESSAGE + ) + + if (fatal) { + System.exit(1) + } + } + } + + @JvmStatic + fun showYesNoQuestion( + editor: Frame?, title: String?, + primary: String?, secondary: String? + ): Int { + if (!Platform.isMacOS()) { + return JOptionPane.showConfirmDialog( + editor, + Toolkit.formatMessage(primary, secondary), //"" + + //"" + primary + "" + + //"
" + secondary, + title, + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE + ) + } else { + val result = showCustomQuestion( + editor, title, primary, secondary, + 0, "Yes", "No" + ) + return if (result == 0) { + JOptionPane.YES_OPTION + } else if (result == 1) { + JOptionPane.NO_OPTION + } else { + JOptionPane.CLOSED_OPTION + } + } + } + + + /** + * @param highlight A valid array index for options[] that specifies the + * default (i.e. safe) choice. + * @return The (zero-based) index of the selected value, -1 otherwise. + */ + @JvmStatic + fun showCustomQuestion( + editor: Frame?, title: String?, + primary: String?, secondary: String?, + highlight: Int, vararg options: String + ): Int { + val result: Any + if (!Platform.isMacOS()) { + return JOptionPane.showOptionDialog( + editor, + Toolkit.formatMessage(primary, secondary), title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, + options, options[highlight] + ) + } else { + val pane = + JOptionPane( + Toolkit.formatMessage(primary, secondary), + JOptionPane.QUESTION_MESSAGE + ) + + pane.options = options + + // highlight the safest option ala apple hig + pane.initialValue = options[highlight] + + val dialog = pane.createDialog(editor, null) + dialog.isVisible = true + + result = pane.value + } + for (i in options.indices) { + if (result != null && result == options[i]) return i + } + return -1 + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + @JvmStatic + @Deprecated("Use log() instead") + fun log(from: Any, message: String) { + if (Base.DEBUG) { + val callingClass = Throwable() + .stackTrace[2] + .className + .formatClassName() + println("$callingClass: $message") + } + } + + @JvmStatic + fun log(message: String?) { + if (Base.DEBUG) { + val callingClass = Throwable() + .stackTrace[2] + .className + .formatClassName() + println("$callingClass$message") + } + } + + @JvmStatic + fun logf(message: String?, vararg args: Any?) { + if (Base.DEBUG) { + val callingClass = Throwable() + .stackTrace[2] + .className + .formatClassName() + System.out.printf("$callingClass$message", *args) + } + } + + @JvmStatic + @JvmOverloads + fun err(message: String?, e: Throwable? = null) { + if (Base.DEBUG) { + if (message != null) { + val callingClass = Throwable() + .stackTrace[4] + .className + .formatClassName() + System.err.println("$callingClass$message") + } + e?.printStackTrace() + } + } + } +} + +// Helper functions to give the base classes a color +fun String.formatClassName() = this + .replace("processing.", "") + .replace(".", "/") + .padEnd(40) + .colorizePathParts() +fun String.colorizePathParts() = split("/").joinToString("/") { part -> + "\u001B[${31 + (part.hashCode() and 0x7).rem(6)}m$part\u001B[0m" +} \ No newline at end of file diff --git a/app/src/processing/app/exec/StreamPump.java b/app/src/processing/app/exec/StreamPump.java index 130a74afd..29786102c 100644 --- a/app/src/processing/app/exec/StreamPump.java +++ b/app/src/processing/app/exec/StreamPump.java @@ -13,6 +13,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import processing.app.Base; +import processing.app.Messages; /** @@ -79,8 +80,7 @@ public class StreamPump implements Runnable { } } catch (final IOException e) { if (Base.DEBUG) { - System.err.println("StreamPump: " + name); - e.printStackTrace(System.err); + Messages.err("StreamPump: " + name, e); // removing for 0190, but need a better way to handle these throw new RuntimeException("Inside " + this + " for " + name, e); } diff --git a/app/src/processing/app/syntax/im/InputMethodSupport.java b/app/src/processing/app/syntax/im/InputMethodSupport.java index 391b96b97..e3323fa11 100644 --- a/app/src/processing/app/syntax/im/InputMethodSupport.java +++ b/app/src/processing/app/syntax/im/InputMethodSupport.java @@ -79,9 +79,7 @@ public class InputMethodSupport implements InputMethodRequests, InputMethodListe @Override public Rectangle getTextLocation(TextHitInfo offset) { - if (Base.DEBUG) { - Messages.log("#Called getTextLocation:" + offset); - } + Messages.log("#Called getTextLocation:" + offset); int line = textArea.getCaretLine(); int offsetX = textArea.getCaretPosition() - textArea.getLineStartOffset(line); // '+1' mean textArea.lineToY(line) + textArea.getPainter().getFontMetrics().getHeight(). @@ -238,9 +236,7 @@ public class InputMethodSupport implements InputMethodRequests, InputMethodListe RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); FontRenderContext frc = g2d.getFontRenderContext(); - if (Base.DEBUG) { - Messages.log("debug: FontRenderContext is Antialiased = " + frc.getAntiAliasingHint()); - } + Messages.log("debug: FontRenderContext is Antialiased = " + frc.getAntiAliasingHint()); return new TextLayout(composedTextString.getIterator(), frc); } diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java index 14be32d68..c8c40ee48 100644 --- a/app/src/processing/app/ui/EditorConsole.java +++ b/app/src/processing/app/ui/EditorConsole.java @@ -276,7 +276,9 @@ public class EditorConsole extends JScrollPane { // components, causing deadlock. Updates are buffered to the console and // displayed at regular intervals on Swing's event-dispatching thread. // (patch by David Mellis) - consoleDoc.appendString(what, err ? errStyle : stdStyle); + // Remove ANSI escape codes from the text before adding it to the console + String clean = what.replaceAll("\u001B\\[[0-9;]*m", ""); + consoleDoc.appendString(clean, err ? errStyle : stdStyle); } } diff --git a/java/src/processing/mode/java/CompletionPanel.java b/java/src/processing/mode/java/CompletionPanel.java index bda85d75e..2ba1eb29e 100644 --- a/java/src/processing/mode/java/CompletionPanel.java +++ b/java/src/processing/mode/java/CompletionPanel.java @@ -287,7 +287,7 @@ public class CompletionPanel { int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1; if (x >= s.length() || x < 0) return null; //TODO: Does this check cause problems? Verify. - if (Base.DEBUG) System.out.print(" x char: " + s.charAt(x)); + Messages.log(" x char: " + s.charAt(x)); String word = String.valueOf(s.charAt(x)); if (s.trim().length() == 1) { diff --git a/java/src/processing/mode/java/PreprocService.java b/java/src/processing/mode/java/PreprocService.java index f70536294..4f9150561 100644 --- a/java/src/processing/mode/java/PreprocService.java +++ b/java/src/processing/mode/java/PreprocService.java @@ -117,7 +117,7 @@ public class PreprocService { running = true; PreprocSketch prevResult = null; CompletableFuture runningCallbacks = null; - Messages.log("PPS: Hi!"); + Messages.log("Hi!"); while (running) { try { try { @@ -127,7 +127,7 @@ public class PreprocService { break; } - Messages.log("PPS: Starting"); + Messages.log("Starting"); prevResult = preprocessSketch(prevResult); @@ -143,7 +143,7 @@ public class PreprocService { synchronized (requestLock) { if (requestQueue.isEmpty()) { runningCallbacks = lastCallback; - Messages.log("PPS: Done"); + Messages.log("Done"); preprocessingTask.complete(prevResult); } } @@ -151,7 +151,7 @@ public class PreprocService { Messages.err("problem in preprocessor service loop", e); } } - Messages.log("PPS: Bye!"); + Messages.log("Bye!"); } /** @@ -188,7 +188,7 @@ public class PreprocService { * Indicate to this service that the sketch libraries have changed. */ public void notifyLibrariesChanged() { - Messages.log("PPS: notified libraries changed"); + Messages.log("notified libraries changed"); librariesChanged.set(true); notifySketchChanged(); } @@ -197,7 +197,7 @@ public class PreprocService { * Indicate to this service that the folder housing sketch code has changed. */ public void notifyCodeFolderChanged() { - Messages.log("PPS: notified code folder changed"); + Messages.log("notified code folder changed"); codeFolderChanged.set(true); notifySketchChanged(); } @@ -216,7 +216,7 @@ public class PreprocService { .thenAcceptBothAsync(lastCallback, (ps, a) -> callback.accept(ps)) // Make sure exception in callback won't cancel whole callback chain .handleAsync((res, e) -> { - if (e != null) Messages.err("PPS: exception in callback", e); + if (e != null) Messages.err("exception in callback", e); return res; }); return lastCallback; diff --git a/java/src/processing/mode/java/runner/Runner.java b/java/src/processing/mode/java/runner/Runner.java index 0e29e18a1..b4dc51770 100644 --- a/java/src/processing/mode/java/runner/Runner.java +++ b/java/src/processing/mode/java/runner/Runner.java @@ -253,19 +253,19 @@ public class Runner implements MessageConsumer { // while (!available) { while (true) { try { - Messages.log(getClass().getName() + " attempting to attach to VM"); + Messages.log("attempting to attach to VM"); synchronized (cancelLock) { vm = connector.attach(arguments); if (cancelled && vm != null) { // cancelled and connected to the VM, handle closing now - Messages.log(getClass().getName() + " aborting, launch cancelled"); + Messages.log("aborting, launch cancelled"); close(); return false; } } // vm = connector.attach(arguments); if (vm != null) { - Messages.log(getClass().getName() + " attached to the VM"); + Messages.log("attached to the VM"); // generateTrace(); // available = true; return true; @@ -273,17 +273,17 @@ public class Runner implements MessageConsumer { } catch (ConnectException ce) { // This will fire ConnectException (socket not available) until // the VM finishes starting up and opens its socket for us. - Messages.log(getClass().getName() + " socket for VM not ready"); + Messages.log("socket for VM not ready"); // System.out.println("waiting"); // e.printStackTrace(); try { Thread.sleep(100); } catch (InterruptedException ie) { - Messages.err(getClass().getName() + " interrupted", ie); + Messages.err("interrupted", ie); // ie.printStackTrace(sketchErr); } } catch (IOException e) { - Messages.err(getClass().getName() + " while attaching to VM", e); + Messages.err("while attaching to VM", e); } } // } catch (IOException exc) { From 0483e2e83b8c06e458e5ecc2f3e5bcf5f8c71c8f Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 23:29:13 +0100 Subject: [PATCH 008/160] Welcome to Beta screen: Locale fix --- app/src/processing/app/ui/WelcomeToBeta.kt | 4 ++-- app/src/processing/app/ui/theme/Locale.kt | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index 7c75bb803..ddffd42d3 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -91,9 +91,10 @@ class WelcomeToBeta { horizontalArrangement = Arrangement .spacedBy(20.dp) ){ + val locale = LocalLocale.current Image( painter = painterResource("logo.svg"), - contentDescription = "Processing Logo", + contentDescription = locale["beta.logo"], modifier = Modifier .align(Alignment.CenterVertically) .size(100.dp, 100.dp) @@ -107,7 +108,6 @@ class WelcomeToBeta { alignment = Alignment.CenterVertically ) ) { - val locale = LocalLocale.current Text( text = locale["beta.title"], style = MaterialTheme.typography.subtitle1, diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt index 3516b28a3..254c0946c 100644 --- a/app/src/processing/app/ui/theme/Locale.kt +++ b/app/src/processing/app/ui/theme/Locale.kt @@ -4,6 +4,7 @@ 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 @@ -21,10 +22,12 @@ class Locale(language: String = "") : Properties() { } @Deprecated("Use get instead", ReplaceWith("get(key)")) - override fun getProperty(key: String?): String { - return super.getProperty(key) + override fun getProperty(key: String?, default: String): String { + val value = super.getProperty(key, default) + if(value == default) Messages.log("Missing translation for $key") + return value } - operator fun get(key: String): String = getProperty(key) + operator fun get(key: String): String = getProperty(key, key) } val LocalLocale = compositionLocalOf { Locale() } @Composable From 0cec8c9dda394bfe82c6e5b197040f0f2f2ac5f7 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 23:29:33 +0100 Subject: [PATCH 009/160] Create defaults.txt --- app/src/main/resources/defaults.txt | 309 ++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 app/src/main/resources/defaults.txt diff --git a/app/src/main/resources/defaults.txt b/app/src/main/resources/defaults.txt new file mode 100644 index 000000000..6e3e00f0d --- /dev/null +++ b/app/src/main/resources/defaults.txt @@ -0,0 +1,309 @@ +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + +# DO NOT MAKE CHANGES TO THIS FILE!!! + +# These are the default preferences. If you want to modify +# them directly, use the per-user local version of the file: + +# Users -> [username] -> AppData -> Roaming -> +# Processing -> preferences.txt (on Windows 10) + +# ~/Library -> Processing -> preferences.txt (on macOS) + +# ~/.config/processing -> preferences.txt (on Linux) + +# The exact location of your preferences file can be found at +# the bottom of the Preferences window inside Processing. + +# Because AppData and Application Data may be considered +# hidden or system folders on Windows, you'll have to ensure +# that they're visible in order to get at preferences.txt + +# You'll have problems running Processing if you incorrectly +# modify lines in this file. It will probably not start at all. + +# AGAIN, DO NOT ALTER THIS FILE! I'M ONLY YELLING BECAUSE I LOVE YOU! + + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + +# If you don't want users to have their sketchbook default to +# "My Documents/Processing" on Windows and "Documents/Processing" on OS X, +# set this to another path that will be used by default. +# Note that this path must exist already otherwise it won't see +# the sketchbook folder, and will instead assume the sketchbook +# has gone missing, and that it should instead use the default. +# In 4.0, the location has changed. +#sketchbook.path.four= + +# Whether or not to show the Welcome screen for 4.0 +# (It's always available under Help → Welcome) +welcome.four.show = true +welcome.four.seen = false + +# Set 'true' for the default behavior before 4.0, where the +# main tab must have the same name as the sketch folder +editor.sync_folder_and_filename = true + +# By default, contributions are moved to backup folders when +# they are removed or replaced. The backups can be found at +# sketchbook/libraries/old, sketchbook/tools/old, and sketchbook/modes/old + +# true to backup contributions when "Remove" button is pressed +contribution.backup.on_remove = true +# true to backup contributions when installing a newer version +contribution.backup.on_install = true + +recent.count = 10 + +# Default to the native (AWT) file selector where possible +chooser.files.native = true +# We were shutting this off on macOS because it broke Copy/Paste: +# https://github.com/processing/processing/issues/1035 +# But removing again for 4.0 alpha 5, because the JFileChooser is awful, +# and worse on Big Sur, so a bigger problem than the Copy/Paste issue. +# https://github.com/processing/processing4/issues/77 +#chooser.files.native.macos = false + +# set to 'lab' to interpolate theme gradients using L*a*b* color space +theme.gradient.method = rgb + + +# by default, check the processing server for any updates +# (please avoid disabling, this also helps us know basic numbers +# on how many people are using Processing) +update.check = true + +# on windows, automatically associate .pde files with processing.exe +platform.auto_file_type_associations = true + + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + +# default size for the main window +editor.window.width.default = 700 +editor.window.height.default = 600 + +editor.window.width.min = 400 +editor.window.height.min = 500 +# tested as approx 440 on OS X +editor.window.height.min.macos = 450 +# tested to be 515 on Windows XP, this leaves some room +editor.window.height.min.windows = 530 +# tested with Raspberry Pi display +editor.window.height.min.linux = 480 + +# scaling for the interface (to handle Windows and Linux HiDPI displays) +editor.zoom = 100% +# automatically set based on system dpi (only helps on Windows) +editor.zoom.auto = true + +# Use the default monospace font included in lib/fonts. +# (As of Processing 4 alpha 5, that's Source Code Pro) +editor.font.family = processing.mono +editor.font.size = 12 + +# To reset everyone's default, replaced editor.antialias with editor.smooth +# for 2.1. Fonts are unusably gross on OS X (and Linux) w/o smoothing and +# the Oracle JVM, and many longtime users have anti-aliasing turned off. +editor.smooth = true + +# blink the caret by default +editor.caret.blink = true +# change to true to use a block (instead of a bar) +editor.caret.block = false + +# enable ctrl-ins, shift-ins, shift-delete for cut/copy/paste +# on windows and linux, but disable on the mac +editor.keys.alternative_cut_copy_paste = true +editor.keys.alternative_cut_copy_paste.macos = false + +# true if shift-backspace sends the delete character, +# false if shift-backspace just means backspace +editor.keys.shift_backspace_is_delete = false + +# home and end keys should only travel to the start/end of the current line +editor.keys.home_and_end_travel_far = false +# home and end keys move to the first/last non-whitespace character, +# and move to the actual start/end when pressed a second time. +# Only works if editor.keys.home_and_end_travel_far is false. +editor.keys.home_and_end_travel_smart = true +# The OS X HI Guidelines say that home/end are relative to the document, +# but that drives some people nuts. This pref enables/disables it. +editor.keys.home_and_end_travel_far.macos = true + +# Enable/disable support for complex scripts. Used for Japanese and others, +# but disable when not needed, otherwise basic Western European chars break. +editor.input_method_support = false + +# convert tabs to spaces? how many spaces? +editor.tabs.expand = true +editor.tabs.size = 2 + +# Set to true to automatically close [ { ( " and ' +editor.completion.auto_close = false + +# automatically indent each line +editor.indent = true + +# Whether to check files to see if they've been modified externally +editor.watcher = true +# Set true to enable debugging, since this is quirky on others' machines +editor.watcher.debug = false +# The window of time (in milliseconds) in which a change won't be counted +editor.watcher.window = 1500 + +# Format and search engine to use for online queries +search.format = https://google.com/search?q=%s + +# font choice and size for the console +console.font.size = 12 + +# number of lines to show by default +console.lines = 4 + +# Number of blank lines to advance/clear console. +# Note that those lines are also printed in the terminal when +# Processing is executed there. +# Setting to 0 stops this behavior. +console.head_padding = 10 + +# Set to false to disable automatically clearing the console +# each time 'run' is hit +# If one sets it to false, one may also want to set 'console.head_padding' +# to a positive number to separate outputs from different runs. +console.auto_clear = true + +# number of days of history to keep around before cleaning +# setting to 0 will never clean files +console.temp.days = 7 + +# set the maximum number of lines remembered by the console +# the default is 500, lengthen at your own peril +console.scrollback.lines = 500 +console.scrollback.chars = 40000 + +# Any additional Java options when running. +# If you change this and can't run things, it's your own durn fault. +run.options = + +# settings for the -XmsNNNm and -XmxNNNm command line option +run.options.memory = false +run.options.memory.initial = 64 +run.options.memory.maximum = 512 + +# Index of the display to use for running sketches (starts at 1). +# Kept this 1-indexed because older vesions of Processing were setting +# the preference even before it was being used. +# -1 means the default display, 0 means all displays +run.display = -1 + +# set internally because it comes from the system +#run.window.bgcolor= + +# set to false to open a new untitled window when closing the last window +# (otherwise, the environment will quit) +# default to the relative norm for the different platforms, +# but the setting can be changed in the prefs dialog anyway +#sketchbook.closing_last_window_quits = true +#sketchbook.closing_last_window_quits.macos = false + +editor.untitled.prefix=sketch_ +# The old (pre-1.0, back for 2.0) style for default sketch name. +# If you change this, be careful that this will work with your language +# settings. For instance, MMMdd won't work on Korean-language systems +# because it'll insert non-ASCII characters and break the environment. +# https://github.com/processing/processing/issues/322 +editor.untitled.suffix=yyMMdd + +# replace underscores in .pde file names with spaces +sketch.name.replace_underscore = true + +# what to use for generating sketch names (change in the prefs window) +#sketch.name.approach = + +# number of days of build history and other temp files to keep around +# these are kept around for debugging purposes, and in case code is lost +temp.days = 7 + + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + +# whether or not to export as full screen (present) mode +export.application.fullscreen = false + +# whether to show the stop button when exporting to application +export.application.stop = true + +# embed Java by default for lower likelihood of problems +export.application.embed_java = true + +# set to false to no longer delete application folders before export +# (removed from the Preferences windows in 4.0 beta 9) +export.delete_target_folder = true + +# may be useful when attempting to debug the preprocessor +preproc.save_build_files=false + +# allows various preprocessor features to be toggled +# in case they are causing problems + +# preprocessor: pde.g +preproc.color_datatype = true +preproc.web_colors = true +preproc.enhanced_casting = true + +# preprocessor: PdeEmitter.java +preproc.substitute_floats = true + +# PdePreproc.java +# writes out the parse tree as parseTree.xml, which can be usefully +# viewed in (at least) Mozilla or IE. useful when debugging the preprocessor. +preproc.output_parse_tree = false + +# set to the program to be used for opening HTML files, folders, etc. +#launcher.linux = xdg-open + +# FULL SCREEN (PRESENT MODE) +run.present.bgcolor = #666666 +run.present.stop.color = #cccccc + +# PROXIES +# Set a proxy server for folks that require it. This will allow the update +# checker and the contrib manager to run properly in those environments. +# This changed from proxy.host and proxy.port to proxy.http.host and +# proxy.http.port in 3.0a8. In addition, https and socks were added. +proxy.http.host= +proxy.http.port= +proxy.https.host= +proxy.https.port= +proxy.socks.host= +proxy.socks.port= +# Example of usage (replace 'http' with 'https' or 'socks' as needed) +#proxy.http.host=proxy.example.com +#proxy.http.port=8080 +# Whether to use the system proxy by default +proxy.system=true + +# PDE X +pdex.errorCheckEnabled = true +pdex.warningsEnabled = true +pdex.writeErrorLogs = false + +pdex.autoSave.autoSaveEnabled = false +pdex.autoSaveInterval = 5 +pdex.autoSave.promptDisplay = true +pdex.autoSave.autoSaveByDefault = true + +# Enable auto-completion when hitting ctrl-space +pdex.completion = false +# Setting this true will show completions whenever available, not just after ctrl-space +pdex.completion.trigger = false +# Suggest libraries to import when a class is undefined/unavailable +pdex.suggest.imports = true +# Set to false to disable ctrl/cmd-click jump to definition +pdex.inspectMode.hotkey = true From 108f0bb99f8039278a61fdcb3649d843395ecf69 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 6 Feb 2025 23:31:53 +0100 Subject: [PATCH 010/160] Welcome to Beta screen: Msg fix --- build/shared/lib/languages/PDE.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index be680c159..74fd5027e 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -618,7 +618,7 @@ update_check.updates_available.contributions = There are updates available for s # Beta beta.window.title = Welcome to Beta beta.title = Welcome to the Processing Beta -beta.message = Thank you for trying out the new version of Processing. We?re very grateful!\n\nPlease report any bugs on the forums. +beta.message = Thank you for trying out the new version of Processing. We're very grateful!\n\nPlease report any bugs on the forums. beta.button = Got it! # --------------------------------------- From bebf792699542ef3cd610363b35bb4b3c43d5193 Mon Sep 17 00:00:00 2001 From: rishab Date: Thu, 6 Mar 2025 20:51:44 +0530 Subject: [PATCH 011/160] pre multiplied projection matrix --- core/src/processing/opengl/PGraphicsOpenGL.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/processing/opengl/PGraphicsOpenGL.java b/core/src/processing/opengl/PGraphicsOpenGL.java index 88164f43e..2d6dca991 100644 --- a/core/src/processing/opengl/PGraphicsOpenGL.java +++ b/core/src/processing/opengl/PGraphicsOpenGL.java @@ -4481,7 +4481,7 @@ public class PGraphicsOpenGL extends PGraphics { // The minus sign is needed to invert the Y axis. projection.set(x, 0, 0, tx, - 0, -y, 0, ty, + 0, -y, 0, -ty, 0, 0, z, tz, 0, 0, 0, 1); From 34cc4387e4aa6fa6dba99bdeccf589ffb5f8a334 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 7 Mar 2025 15:09:35 +0200 Subject: [PATCH 012/160] feat: Add handleMoveLines method with keystroke support and ActionListeners - Implement handleMoveLines to manage line movement functionality - Add keystroke bindings for triggering line movements - Integrate ActionListeners for responsive UI interactions - Ensure cross-platform compatibility for macOS and Linux --- app/src/processing/app/ui/Editor.java | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index f87ba4ee1..5d54fd17b 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -821,6 +821,17 @@ public abstract class Editor extends JFrame implements RunnerListener { editMenuUpdatable.add(action); menu.add(item); + item = new JMenuItem("Move Selected Lines Up"); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK)); + item.addActionListener(e -> handleMoveLines(true)); + menu.add(item); + + item = new JMenuItem("Move Selected Lines Down"); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK)); + item.addActionListener(e -> handleMoveLines(false)); + menu.add(item); + + // Update copy/cut state on selection/de-selection menu.addMenuListener(new MenuListener() { // UndoAction and RedoAction do this for themselves. @@ -1865,6 +1876,7 @@ public abstract class Editor extends JFrame implements RunnerListener { } + public void handleIndent() { handleIndentOutdent(true); } @@ -1918,6 +1930,84 @@ public abstract class Editor extends JFrame implements RunnerListener { stopCompoundEdit(); sketch.setModified(true); } + /** + * Moves the selected lines up or down in the text editor. + * + *

If {@code moveUp} is true, the selected lines are moved up. If false, they move down.

+ *

This method ensures proper selection updates and handles edge cases like moving + * the first or last line.

+ * + * @param moveUp {@code true} to move the selection up, {@code false} to move it down. + */ + public void handleMoveLines(boolean moveUp) { + startCompoundEdit(); + + int startLine = textarea.getSelectionStartLine(); + int stopLine = textarea.getSelectionStopLine(); + + // Adjust selection if the last line isn't fully selected + if (startLine != stopLine && + textarea.getSelectionStop() == textarea.getLineStartOffset(stopLine)) { + stopLine--; + } + + int replacedLine = moveUp ? startLine - 1 : stopLine + 1; + if (replacedLine < 0 || replacedLine >= textarea.getLineCount()) { + stopCompoundEdit(); + return; + } + + final String source = textarea.getText(); // Get full text from textarea + + int replaceStart = textarea.getLineStartOffset(replacedLine); + int replaceEnd = textarea.getLineStopOffset(replacedLine); + if (replaceEnd > source.length()) { + replaceEnd = source.length(); + } + + int selectionStart = textarea.getLineStartOffset(startLine); + int selectionEnd = textarea.getLineStopOffset(stopLine); + if (selectionEnd > source.length()) { + selectionEnd = source.length(); + } + + String replacedText = source.substring(replaceStart, replaceEnd); + String selectedText = source.substring(selectionStart, selectionEnd); + + if (replacedLine == textarea.getLineCount() - 1) { + replacedText += "\n"; + selectedText = selectedText.substring(0, Math.max(0, selectedText.length() - 1)); + } else if (stopLine == textarea.getLineCount() - 1) { + selectedText += "\n"; + replacedText = replacedText.substring(0, Math.max(0, replacedText.length() - 1)); + } + + int newSelectionStart, newSelectionEnd; + if (moveUp) { + textarea.select(selectionStart, selectionEnd); + textarea.setSelectedText(replacedText); // Use setSelectedText() + + textarea.select(replaceStart, replaceEnd); + textarea.setSelectedText(selectedText); + + newSelectionStart = textarea.getLineStartOffset(startLine - 1); + newSelectionEnd = textarea.getLineStopOffset(stopLine - 1); + } else { + textarea.select(replaceStart, replaceEnd); + textarea.setSelectedText(selectedText); + + textarea.select(selectionStart, selectionEnd); + textarea.setSelectedText(replacedText); + + newSelectionStart = textarea.getLineStartOffset(startLine + 1); + newSelectionEnd = stopLine + 1 < textarea.getLineCount() + ? Math.min(textarea.getLineStopOffset(stopLine + 1), source.length()) + : textarea.getLineStopOffset(stopLine); // Prevent out-of-bounds + } + + textarea.select(newSelectionStart, newSelectionEnd); + stopCompoundEdit(); + } static public boolean checkParen(char[] array, int index, int stop) { From 938109dbbd786d784b7515d542cab9f0001b4279 Mon Sep 17 00:00:00 2001 From: yehiarasheed Date: Fri, 7 Mar 2025 16:43:22 +0200 Subject: [PATCH 013/160] Fix selection bug in handleMoveLines on macOS by using invokeLater() Previously, moving lines down on macOS caused incorrect selection, moving the cursor to the end of the enclosing braces instead of properly selecting the moved line. This issue did not occur on Windows. The fix ensures that selection updates happen inside Swing's event dispatch thread by wrapping the selection logic in SwingUtilities.invokeLater(). This guarantees proper selection behavior across all platforms. Additionally, updated the JavaDoc for the method to reflect the fix and clarify the behavior of the selection update. Tested on Windows and macOS. --- app/src/processing/app/ui/Editor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 5d54fd17b..9736787d2 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1936,6 +1936,8 @@ public abstract class Editor extends JFrame implements RunnerListener { *

If {@code moveUp} is true, the selected lines are moved up. If false, they move down.

*

This method ensures proper selection updates and handles edge cases like moving * the first or last line.

+ *

This operation is undo/redoable, allowing the user to revert the action using + * {@code Ctrl/Cmd + Z} (Undo) and redo with {@code Ctrl/Cmd + Y} (Redo).

* * @param moveUp {@code true} to move the selection up, {@code false} to move it down. */ @@ -2005,7 +2007,7 @@ public abstract class Editor extends JFrame implements RunnerListener { : textarea.getLineStopOffset(stopLine); // Prevent out-of-bounds } - textarea.select(newSelectionStart, newSelectionEnd); + SwingUtilities.invokeLater(() -> textarea.select(newSelectionStart, newSelectionEnd)); stopCompoundEdit(); } From 77b354d403e76194e783a04a3b82072c82349834 Mon Sep 17 00:00:00 2001 From: yehiarasheed Date: Fri, 7 Mar 2025 17:23:56 +0200 Subject: [PATCH 014/160] Update JavaDoc to clarify redo keybinding on macOS Updated the JavaDoc to reflect that the redo command on macOS is {@code Shift + Cmd + Z}, not {@code Cmd + Y}, and may vary on other platforms. This ensures the documentation is accurate and aligns with the actual keybindings. No functional code changes were made. --- app/src/processing/app/ui/Editor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 9736787d2..3817c4294 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1937,7 +1937,8 @@ public abstract class Editor extends JFrame implements RunnerListener { *

This method ensures proper selection updates and handles edge cases like moving * the first or last line.

*

This operation is undo/redoable, allowing the user to revert the action using - * {@code Ctrl/Cmd + Z} (Undo) and redo with {@code Ctrl/Cmd + Y} (Redo).

+ * {@code Ctrl/Cmd + Z} (Undo). Redo functionality is available through the + * keybinding {@code Ctrl/Cmd + Z} on Windows/Linux and {@code Shift + Cmd + Z} on macOS.

* * @param moveUp {@code true} to move the selection up, {@code false} to move it down. */ From 801b02b52b46c238772394b3765329587301965d Mon Sep 17 00:00:00 2001 From: rishab Date: Sat, 8 Mar 2025 20:40:09 +0530 Subject: [PATCH 015/160] added keyEvent tests --- core/src/processing/core/PApplet.java | 1 + .../processing/core/PAppletKeyEventTest.java | 142 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 core/test/processing/core/PAppletKeyEventTest.java diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index 24c0d5d39..d96784a3f 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -2842,6 +2842,7 @@ public class PApplet implements PConstants { public void focusLost() { // TODO: if user overrides this without calling super it's not gonna work pressedKeys.clear(); + keyPressed = false; } diff --git a/core/test/processing/core/PAppletKeyEventTest.java b/core/test/processing/core/PAppletKeyEventTest.java new file mode 100644 index 000000000..361010d3f --- /dev/null +++ b/core/test/processing/core/PAppletKeyEventTest.java @@ -0,0 +1,142 @@ +package processing.core; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import processing.event.KeyEvent; +import java.util.HashSet; +import java.util.Iterator; + +public class PAppletKeyEventTest { + + private static final int SHIFT_MASK = 1; + private static final int CTRL_MASK = 2; + private static final int ALT_MASK = 4; + + private PApplet applet; + + @Before + public void setup() { + applet = new PApplet(); + } + + @Test + public void testSingleKeyPressAndRelease() { + KeyEvent pressEvent = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 'a', 65, false); + applet.handleKeyEvent(pressEvent); + Assert.assertEquals(1, applet.pressedKeys.size()); + + KeyEvent releaseEvent = new KeyEvent(null, 0L, KeyEvent.RELEASE, 0, 'a', 65, false); + applet.handleKeyEvent(releaseEvent); + Assert.assertEquals(0, applet.pressedKeys.size()); + Assert.assertFalse(applet.keyPressed); + } + + @Test + public void testShiftAndLetterSequence() { + KeyEvent pressA = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 'a', 65, false); + applet.handleKeyEvent(pressA); + + KeyEvent pressShift = new KeyEvent(null, 0L, KeyEvent.PRESS, SHIFT_MASK, 'A', 16, false); + applet.handleKeyEvent(pressShift); + + KeyEvent releaseA = new KeyEvent(null, 0L, KeyEvent.RELEASE, SHIFT_MASK, 'A', 65, false); + applet.handleKeyEvent(releaseA); + + KeyEvent releaseShift = new KeyEvent(null, 0L, KeyEvent.RELEASE, 0, 'A', 16, false); + applet.handleKeyEvent(releaseShift); + + Assert.assertFalse("keyPressed should be false after all keys released", applet.keyPressed); + Assert.assertEquals("pressedKeys should be empty", true, applet.pressedKeys.isEmpty()); + } + + @Test + public void testControlAndLetterSequence() { + KeyEvent pressCtrl = new KeyEvent(null, 0L, KeyEvent.PRESS, CTRL_MASK, '\0', 17, false); + applet.handleKeyEvent(pressCtrl); + + KeyEvent pressC = new KeyEvent(null, 0L, KeyEvent.PRESS, CTRL_MASK, (char)3, 67, false); + applet.handleKeyEvent(pressC); + + KeyEvent releaseC = new KeyEvent(null, 0L, KeyEvent.RELEASE, CTRL_MASK, 'c', 67, false); + applet.handleKeyEvent(releaseC); + + KeyEvent releaseCtrl = new KeyEvent(null, 0L, KeyEvent.RELEASE, 0, '\0', 17, false); + applet.handleKeyEvent(releaseCtrl); + + Assert.assertFalse("keyPressed should be false after all keys released", applet.keyPressed); + Assert.assertTrue("pressedKeys should be empty", applet.pressedKeys.isEmpty()); + } + + @Test + public void testAltAndLetterSequence() { + KeyEvent pressV = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 'v', 86, false); + applet.handleKeyEvent(pressV); + + KeyEvent pressAlt = new KeyEvent(null, 0L, KeyEvent.PRESS, ALT_MASK, 'v', 18, false); + applet.handleKeyEvent(pressAlt); + + KeyEvent releaseV = new KeyEvent(null, 0L, KeyEvent.RELEASE, ALT_MASK, 'v', 86, false); + applet.handleKeyEvent(releaseV); + + KeyEvent releaseAlt = new KeyEvent(null, 0L, KeyEvent.RELEASE, 0, 'v', 18, false); + applet.handleKeyEvent(releaseAlt); + + Assert.assertFalse("keyPressed should be false after all keys released", applet.keyPressed); + Assert.assertEquals("pressedKeys should be empty", true, applet.pressedKeys.isEmpty()); + } + + @Test + public void testKeyRepeat() { + applet.keyRepeatEnabled = false; + + KeyEvent pressR = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 'r', 82, false); + applet.handleKeyEvent(pressR); + + KeyEvent repeatR = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 'r', 82, true); + applet.handleKeyEvent(repeatR); + + Assert.assertTrue("keyPressed should be true after key press", applet.keyPressed); + Assert.assertEquals("pressedKeys should have 1 entry", 1, applet.pressedKeys.size()); + + KeyEvent releaseR = new KeyEvent(null, 0L, KeyEvent.RELEASE, 0, 'r', 82, false); + applet.handleKeyEvent(releaseR); + + Assert.assertFalse("keyPressed should be false after key release", applet.keyPressed); + Assert.assertEquals("pressedKeys should be empty", true, applet.pressedKeys.isEmpty()); + } + + @Test + public void testKeyRepeatEnabled() { + applet.keyRepeatEnabled = true; + + KeyEvent pressT = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 't', 84, false); + applet.handleKeyEvent(pressT); + + KeyEvent repeatT = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 't', 84, true); + applet.handleKeyEvent(repeatT); + + Assert.assertTrue("keyPressed should be true with key repeat enabled", applet.keyPressed); + Assert.assertEquals("pressedKeys should have 1 entry", 1, applet.pressedKeys.size()); + + KeyEvent releaseT = new KeyEvent(null, 0L, KeyEvent.RELEASE, 0, 't', 84, false); + applet.handleKeyEvent(releaseT); + + Assert.assertFalse("keyPressed should be false after key release", applet.keyPressed); + Assert.assertEquals("pressedKeys should be empty", true, applet.pressedKeys.isEmpty()); + } + + @Test + public void testKeyFocusLost() { + KeyEvent pressF = new KeyEvent(null, 0L, KeyEvent.PRESS, 0, 'f', 70, false); + applet.handleKeyEvent(pressF); + + Assert.assertTrue("keyPressed should be true after key press", applet.keyPressed); + Assert.assertEquals("pressedKeys should have 1 entry", 1, applet.pressedKeys.size()); + + applet.focusLost(); + + Assert.assertFalse("keyPressed should be false after focus lost", applet.keyPressed); + Assert.assertEquals("pressedKeys should be empty after focus lost", true, applet.pressedKeys.isEmpty()); + } +} From be9e6b8fac98ee6ad7cd120228fb30f1e4840087 Mon Sep 17 00:00:00 2001 From: rishab Date: Sat, 8 Mar 2025 21:08:33 +0530 Subject: [PATCH 016/160] minor changes --- core/test/processing/core/PAppletKeyEventTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/test/processing/core/PAppletKeyEventTest.java b/core/test/processing/core/PAppletKeyEventTest.java index 361010d3f..f40511ec5 100644 --- a/core/test/processing/core/PAppletKeyEventTest.java +++ b/core/test/processing/core/PAppletKeyEventTest.java @@ -4,8 +4,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import processing.event.KeyEvent; -import java.util.HashSet; -import java.util.Iterator; public class PAppletKeyEventTest { From 30dddff7608558155c6427934dd279c5636275ac Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Mar 2025 19:12:45 +0100 Subject: [PATCH 017/160] macOS distribution --- app/build.gradle.kts | 59 +++++++++++++++++++++++++---- app/macos/background.png | Bin 0 -> 14866 bytes app/{ => macos}/entitlements.plist | 0 app/{ => macos}/info.plist | 0 4 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 app/macos/background.png rename app/{ => macos}/entitlements.plist (100%) rename app/{ => macos}/info.plist (100%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e7ef2e5b3..0e01f30d1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,8 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform 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 +import org.jetbrains.kotlin.fir.scopes.impl.overrides plugins{ id("java") @@ -51,23 +53,24 @@ compose.desktop { packageName = "Processing" macOS{ - bundleID = "org.processing.app" - iconFile = project.file("../build/macos/processing.icns") + bundleID = "${rootProject.group}.app" + iconFile = rootProject.file("build/macos/processing.icns") infoPlist{ - extraKeysRawXml = layout.projectDirectory.file("info.plist").asFile.readText() + extraKeysRawXml = file("macos/info.plist").readText() } - entitlementsFile.set(project.file("entitlements.plist")) - runtimeEntitlementsFile.set(project.file("entitlements.plist")) + entitlementsFile.set(file("macos/entitlements.plist")) + runtimeEntitlementsFile.set(file("macos/entitlements.plist")) + appStore = true } windows{ - iconFile = project.file("../build/windows/processing.ico") + iconFile = rootProject.file("build/windows/processing.ico") menuGroup = "Processing" upgradeUuid = "89d8d7fe-5602-4b12-ba10-0fe78efbd602" } linux { appCategory = "Programming" menuGroup = "Processing" - iconFile = project.file("../build/linux/processing.png") + iconFile = rootProject.file("build/linux/processing.png") // Fix fonts on some Linux distributions jvmArgs("-Dawt.useSystemAAFontSettings=on") @@ -105,6 +108,48 @@ tasks.compileJava{ options.encoding = "UTF-8" } +tasks.register("installCreateDmg") { + commandLine("brew", "install", "--quiet", "create-dmg") +} +tasks.register("packageCustomDmg"){ + group = "compose desktop" + + val distributable = tasks.named("createDistributable").get() + dependsOn(distributable, "installCreateDmg") + + val packageName = distributable.packageName.get() + val dir = distributable.destinationDir.get() + val dmg = dir.file("../dmg/$packageName-$version.dmg").asFile + val app = dir.file("$packageName.app").asFile + + dmg.parentFile.deleteRecursively() + dmg.parentFile.mkdirs() + + commandLine("brew", "install", "--quiet", "create-dmg") + + commandLine("create-dmg", + "--volname", packageName, + "--volicon", rootProject.file("build/macos/processing.icns"), + "--background", file("macos/background.png"), + "--icon", "$packageName.app", "200", "200", + "--window-pos", "200", "200", + "--window-size", "775", "485", + "--app-drop-link", "500", "200", + "--hide-extension", "$packageName.app", + dmg, + app + ) +} + +afterEvaluate{ + tasks.named("packageDmg").configure{ + dependsOn("packageCustomDmg") + group = "compose desktop" + actions = emptyList() + } +} + + // LEGACY TASKS // Most of these are shims to be compatible with the old build system diff --git a/app/macos/background.png b/app/macos/background.png new file mode 100644 index 0000000000000000000000000000000000000000..765619b73dccc7ef89a8cd602a2573d9e89d9c95 GIT binary patch literal 14866 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 Date: Sun, 9 Mar 2025 19:14:28 +0100 Subject: [PATCH 018/160] Switch build to github hosted linux --- .github/workflows/build-gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml index e53355d29..7451e2df7 100644 --- a/.github/workflows/build-gradle.yml +++ b/.github/workflows/build-gradle.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: include: - - os: [self-hosted, linux, ARM64] + - os: ubuntu-24.04-arm os_prefix: linux arch: aarch64 - os: ubuntu-latest From 64a3b83db4e959fdeb34af8ded93a90aad3e781c Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Mar 2025 19:25:49 +0100 Subject: [PATCH 019/160] macOS Distribution fixes --- app/build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e01f30d1..c72079f00 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -109,9 +109,11 @@ tasks.compileJava{ } tasks.register("installCreateDmg") { - commandLine("brew", "install", "--quiet", "create-dmg") + onlyIf { org.gradle.internal.os.OperatingSystem.current().isMacOsX } + commandLine("arch", "-arm64", "brew", "install", "--quiet", "create-dmg") } tasks.register("packageCustomDmg"){ + onlyIf { org.gradle.internal.os.OperatingSystem.current().isMacOsX } group = "compose desktop" val distributable = tasks.named("createDistributable").get() From 34b90c7cbee0d43ff36b85c6f92f3c7e57fc86c8 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 9 Mar 2025 23:58:43 +0100 Subject: [PATCH 020/160] Windows Packaging --- .gitignore | 1 + app/build.gradle.kts | 15 ++++++++++++++- app/windows/Processing.wixproj | 5 +++++ app/windows/Processing.wxs | 31 +++++++++++++++++++++++++++++++ app/windows/background.png | Bin 0 -> 3811 bytes app/windows/banner.png | Bin 0 -> 824 bytes app/windows/license.rtf | 1 + core/build.gradle.kts | 3 +++ 8 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 app/windows/Processing.wixproj create mode 100644 app/windows/Processing.wxs create mode 100644 app/windows/background.png create mode 100644 app/windows/banner.png create mode 100644 app/windows/license.rtf diff --git a/.gitignore b/.gitignore index ebdb29b67..7faa26d70 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ java/build/ /core/examples/build .build/ +/app/windows/obj diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c72079f00..13d2a66fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -143,16 +143,29 @@ tasks.register("packageCustomDmg"){ ) } +tasks.register("packageCustomMsi"){ + workingDir = file("windows") + commandLine( + "dotnet", + "build", + "/p:Platform=x64" + ) +} + afterEvaluate{ tasks.named("packageDmg").configure{ dependsOn("packageCustomDmg") group = "compose desktop" actions = emptyList() } + tasks.named("packageMsi").configure{ + dependsOn("packageCustomMsi") + group = "compose desktop" + actions = emptyList() + } } - // LEGACY TASKS // Most of these are shims to be compatible with the old build system // They should be removed in the future, as we work towards making things more Gradle-native diff --git a/app/windows/Processing.wixproj b/app/windows/Processing.wixproj new file mode 100644 index 000000000..20751a6a9 --- /dev/null +++ b/app/windows/Processing.wixproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/windows/Processing.wxs b/app/windows/Processing.wxs new file mode 100644 index 000000000..e3d6bc5e1 --- /dev/null +++ b/app/windows/Processing.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/windows/background.png b/app/windows/background.png new file mode 100644 index 0000000000000000000000000000000000000000..7345e853daf031b1b5be6663b06ba40773df6ede GIT binary patch literal 3811 zcmeAS@N?(olHy`uVBq!ia0y~yV7|q`z*NA&1{5iKE?EtvI14-?iy0WiR6&^0Gf3qF zP>``W$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di415)yE{-7;jBl?yGCDYjI2`m>tdyBj zJi%SrYtQX-TV}l7b3dO!>`TmX1_p;*d7yp;WuT=D0z$b!lB3fMNVd3014*Yvyg*X% zBG54n6Ec{A%00F)0+p+bDjy9Q@)N;~zR|Qan#)EDl+mJgv{o6dZ%3OJqm8c7_T^~H gd$f}=)pmvFEF$*PxLE { duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} +tasks.compileJava{ + options.encoding = "UTF-8" } \ No newline at end of file From 5366ac9ed72a1e508603bc218b8fc4db3f8d7c2f Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 00:36:45 +0100 Subject: [PATCH 021/160] Windows Distribution Fixes --- app/build.gradle.kts | 5 ++++- app/windows/Processing.wixproj | 4 ++++ app/windows/Processing.wxs | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 13d2a66fd..e4941d2d8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -145,10 +145,13 @@ tasks.register("packageCustomDmg"){ tasks.register("packageCustomMsi"){ workingDir = file("windows") + group = "compose desktop" + commandLine( "dotnet", "build", - "/p:Platform=x64" + "/p:Platform=x64", + "/p:DefineConstants=\"Version=$version;\"" ) } diff --git a/app/windows/Processing.wixproj b/app/windows/Processing.wixproj index 20751a6a9..3dcd4695f 100644 --- a/app/windows/Processing.wixproj +++ b/app/windows/Processing.wixproj @@ -1,4 +1,8 @@ + + ..\build\compose\binaries\main\msi + Processing-$(Version) + diff --git a/app/windows/Processing.wxs b/app/windows/Processing.wxs index e3d6bc5e1..c74613e80 100644 --- a/app/windows/Processing.wxs +++ b/app/windows/Processing.wxs @@ -1,5 +1,5 @@ - + @@ -10,7 +10,7 @@ - + From f1cd662b6875dc09afdec114566f813d19aabdd5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 00:44:33 +0100 Subject: [PATCH 022/160] Fixed missing dependencie --- app/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e4941d2d8..1547524f1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -144,6 +144,8 @@ tasks.register("packageCustomDmg"){ } tasks.register("packageCustomMsi"){ + onlyIf { org.gradle.internal.os.OperatingSystem.current().isWindows } + dependsOn("createDistributable") workingDir = file("windows") group = "compose desktop" From 07b5c8c2db986ea1327be933327312dc4213b186 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed Date: Mon, 10 Mar 2025 16:32:14 +0200 Subject: [PATCH 023/160] Improved line moving functionality in Editor - Updated line moving logic to select until the end of the line only, avoiding selection of the new line character. - Added functionality to move lines without selecting anything if no text is selected. --- app/src/processing/app/ui/Editor.java | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 3817c4294..bd1bf0fae 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1944,6 +1944,15 @@ public abstract class Editor extends JFrame implements RunnerListener { */ public void handleMoveLines(boolean moveUp) { startCompoundEdit(); + boolean isSelected = false; + + if(textarea.isSelectionActive()) + isSelected = true; + + int caretPos = textarea.getCaretPosition(); + int currentLine = textarea.getCaretLine(); + int lineStart = textarea.getLineStartOffset(currentLine); + int column = caretPos - lineStart; int startLine = textarea.getSelectionStartLine(); int stopLine = textarea.getSelectionStopLine(); @@ -2007,11 +2016,22 @@ public abstract class Editor extends JFrame implements RunnerListener { ? Math.min(textarea.getLineStopOffset(stopLine + 1), source.length()) : textarea.getLineStopOffset(stopLine); // Prevent out-of-bounds } - - SwingUtilities.invokeLater(() -> textarea.select(newSelectionStart, newSelectionEnd)); stopCompoundEdit(); - } + if(isSelected) + SwingUtilities.invokeLater(() -> { + textarea.select(newSelectionStart, newSelectionEnd-1); + }); + else if (replacedLine >= 0 && replacedLine < textarea.getLineCount()) { + int replacedLineStart = textarea.getLineStartOffset(replacedLine); + int replacedLineEnd = textarea.getLineStopOffset(replacedLine); + + // Ensure caret stays within bounds of the new line + int newCaretPos = Math.min(replacedLineStart + column, replacedLineEnd - 1); + + SwingUtilities.invokeLater(() -> textarea.setCaretPosition(newCaretPos)); + } +} static public boolean checkParen(char[] array, int index, int stop) { while (index < stop) { From dacc37539a2fc122235026a6eebdea174e93a331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20de=20Courville?= Date: Mon, 10 Mar 2025 21:30:19 +0100 Subject: [PATCH 024/160] Fix typo in core/README.md Remove that sad and lonely "In" --- core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/README.md b/core/README.md index bf300f897..2f4617cea 100644 --- a/core/README.md +++ b/core/README.md @@ -63,7 +63,7 @@ dependencies { ``` ## Developing for Core -The easiest way to develop for core, without the need to build the whole project, is to use the `examples/src` sketches. In +The easiest way to develop for core, without the need to build the whole project, is to use the `examples/src` sketches. ## PGraphics Modes Documentation on how to develop graphics modes as a library should go here. From 9d257de0b9668e436a558c6f2844b51b42792519 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 12:34:47 +0100 Subject: [PATCH 025/160] Only on Linux Snap Distribution Fix missing snapcraft Fix missing lxd Fix missing lxd --- .github/workflows/build-gradle.yml | 7 ++++ app/build.gradle.kts | 58 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml index 7451e2df7..8dfbfea76 100644 --- a/.github/workflows/build-gradle.yml +++ b/.github/workflows/build-gradle.yml @@ -41,6 +41,12 @@ jobs: architecture: ${{ matrix.arch }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + - name: Install Snapcraft + if: runner.os == 'Linux' + uses: samuelmeuli/action-snapcraft@v3 + - name: Install LXD + if: runner.os == 'Linux' + uses: canonical/setup-lxd@main - name: Build with Gradle run: ./gradlew packageDistributionForCurrentOS - name: Add instructions @@ -56,4 +62,5 @@ jobs: ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt ./app/build/compose/binaries/main/msi/Processing-*.msi ./app/build/compose/binaries/main/deb/processing*.deb + ./app/build/compose/binaries/main/processing*.snap retention-days: 1 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1547524f1..06005c2af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -157,6 +157,61 @@ tasks.register("packageCustomMsi"){ ) } +tasks.register("generateSnapConfiguration"){ + onlyIf { org.gradle.internal.os.OperatingSystem.current().isLinux } + val distributable = tasks.named("createDistributable").get() + dependsOn(distributable) + + val arch = when (System.getProperty("os.arch")) { + "amd64", "x86_64" -> "amd64" + else -> System.getProperty("os.arch") + } + + val dir = distributable.destinationDir.get() + val content = """ + name: ${rootProject.name} + version: ${rootProject.version} + base: core22 + summary: A creative coding editor + description: | + Processing is a flexible software sketchbook and a programming language designed for learning how to code. + confinement: strict + + apps: + processing: + command: opt/processing/bin/Processing + desktop: opt/processing/lib/processing-Processing.desktop + plugs: + - desktop + - desktop-legacy + - wayland + - x11 + + parts: + processing: + plugin: dump + source: deb/processing_$version-1_$arch.deb + source-type: deb + stage-packages: + - openjdk-17-jdk + override-prime: | + snapcraftctl prime + chmod -R +x opt/processing/lib/app/resources/jdk-* + """.trimIndent() + dir.file("../snapcraft.yaml").asFile.writeText(content) +} + +tasks.register("packageSnap"){ + onlyIf { org.gradle.internal.os.OperatingSystem.current().isLinux } + dependsOn("packageDeb", "generateSnapConfiguration") + group = "compose desktop" + + val distributable = tasks.named("createDistributable").get() + workingDir = distributable.destinationDir.dir("../").get().asFile + + commandLine("snapcraft") +} + afterEvaluate{ tasks.named("packageDmg").configure{ dependsOn("packageCustomDmg") @@ -168,6 +223,9 @@ afterEvaluate{ group = "compose desktop" actions = emptyList() } + tasks.named("packageDistributionForCurrentOS").configure { + dependsOn("packageSnap") + } } From aa4b0e329fb40b3f5b5a86b9d5c42d5b119b019b Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 13:23:01 +0100 Subject: [PATCH 026/160] Linux Snap Fixes --- .github/workflows/build-gradle.yml | 6 ++++++ app/build.gradle.kts | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml index 8dfbfea76..55e5d9230 100644 --- a/.github/workflows/build-gradle.yml +++ b/.github/workflows/build-gradle.yml @@ -62,5 +62,11 @@ jobs: ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt ./app/build/compose/binaries/main/msi/Processing-*.msi ./app/build/compose/binaries/main/deb/processing*.deb + retention-days: 1 + - name: Upload Snap + uses: actions/upload-artifact@v4 + with: + name: processing-${{ github.ref_name }}-${{github.sha}}-${{ matrix.os_prefix }}-${{ matrix.arch }}-snap + path: | ./app/build/compose/binaries/main/processing*.snap retention-days: 1 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 06005c2af..1c1ea1024 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -164,6 +164,7 @@ tasks.register("generateSnapConfiguration"){ val arch = when (System.getProperty("os.arch")) { "amd64", "x86_64" -> "amd64" + "aarch64" -> "arm64" else -> System.getProperty("os.arch") } From cae9671f1507ad98ec9c1e090357b8341181e259 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 16:55:15 +0100 Subject: [PATCH 027/160] Action cleanup --- .github/workflows/build-gradle.yml | 62 ++++++++++++---------- .github/workflows/maven.yml | 37 ------------- .github/workflows/pull_request-gradle.yml | 50 +++++++++++------ .github/workflows/release-gradle.yml | 48 ++++++++++------- app/build.gradle.kts | 19 ++++++- app/macos/volume.icns | Bin 0 -> 174924 bytes 6 files changed, 116 insertions(+), 100 deletions(-) delete mode 100644 .github/workflows/maven.yml create mode 100644 app/macos/volume.icns diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml index 55e5d9230..254433edd 100644 --- a/.github/workflows/build-gradle.yml +++ b/.github/workflows/build-gradle.yml @@ -1,4 +1,4 @@ -name: Pre-releases with Gradle +name: Branch Builds on: push: paths-ignore: @@ -6,11 +6,26 @@ on: - '.all-contributorsrc' jobs: + test: + runs-on: ubuntu-latest + name: Test Processing + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Install Java + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build with Gradle + run: ./gradlew test build: - name: Create Pre-release for ${{ matrix.os_prefix }} (${{ matrix.arch }}) + name: (${{ matrix.os_prefix }}/${{ matrix.arch }}) Create Processing Build runs-on: ${{ matrix.os }} - permissions: - contents: write + needs: test strategy: fail-fast: false matrix: @@ -18,19 +33,31 @@ jobs: - os: ubuntu-24.04-arm os_prefix: linux arch: aarch64 + binary: processing*.snap - os: ubuntu-latest os_prefix: linux arch: x64 + binary: processing*.snap - os: windows-latest os_prefix: windows arch: x64 + binary: msi/Processing-*.msi - os: macos-latest os_prefix: macos arch: x64 + binary: dmg/Processing-*.dmg - os: macos-latest os_prefix: macos arch: aarch64 + binary: dmg/Processing-*.dmg steps: + - name: Install Snapcraft + if: runner.os == 'Linux' + uses: samuelmeuli/action-snapcraft@v3 + - name: Install LXD + if: runner.os == 'Linux' + uses: canonical/setup-lxd@main + - name: Checkout Repository uses: actions/checkout@v4 - name: Install Java @@ -41,32 +68,13 @@ jobs: architecture: ${{ matrix.arch }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Install Snapcraft - if: runner.os == 'Linux' - uses: samuelmeuli/action-snapcraft@v3 - - name: Install LXD - if: runner.os == 'Linux' - uses: canonical/setup-lxd@main + - name: Build with Gradle run: ./gradlew packageDistributionForCurrentOS - - name: Add instructions - if: ${{ matrix.os_prefix == 'macos' }} - run: | - echo "run 'xattr -d com.apple.quarantine Processing-${version}.dmg' to remove the quarantine flag" > ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt + - name: Add artifact uses: actions/upload-artifact@v4 with: - name: processing-${{ github.ref_name }}-${{github.sha}}-${{ matrix.os_prefix }}-${{ matrix.arch }}-gradle - path: | - ./app/build/compose/binaries/main/dmg/Processing-*.dmg - ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt - ./app/build/compose/binaries/main/msi/Processing-*.msi - ./app/build/compose/binaries/main/deb/processing*.deb - retention-days: 1 - - name: Upload Snap - uses: actions/upload-artifact@v4 - with: - name: processing-${{ github.ref_name }}-${{github.sha}}-${{ matrix.os_prefix }}-${{ matrix.arch }}-snap - path: | - ./app/build/compose/binaries/main/processing*.snap + name: processing-${{ matrix.os_prefix }}-${{ matrix.arch }}-br_${{ github.ref_name }} retention-days: 1 + path: app/build/compose/binaries/main/${{ matrix.binary }} \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index b66dcdeed..000000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Maven Release -on: - workflow_dispatch: - inputs: - version: - description: 'Version to release' - required: true - default: '1.0.0' - - -jobs: - publish: - name: Create Processing Core Release on Maven Central - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - name: Build with Gradle - run: ./gradlew publish - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} - - ORG_GRADLE_PROJECT_version: ${{ github.event.inputs.version }} \ No newline at end of file diff --git a/.github/workflows/pull_request-gradle.yml b/.github/workflows/pull_request-gradle.yml index 11ae6f05a..8c8f6e054 100644 --- a/.github/workflows/pull_request-gradle.yml +++ b/.github/workflows/pull_request-gradle.yml @@ -7,12 +7,26 @@ on: - main jobs: - build: - name: Create Pull Request Build for ${{ matrix.os_prefix }} (${{ matrix.arch }}) + test: + runs-on: ubuntu-latest + name: Test Processing + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Install Java + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build with Gradle + run: ./gradlew test + build: + name: (${{ matrix.os_prefix }}/${{ matrix.arch }}) Create Processing Build runs-on: ${{ matrix.os }} - permissions: - pull-requests: write - contents: read + needs: test strategy: fail-fast: false matrix: @@ -20,19 +34,31 @@ jobs: - os: ubuntu-24.04-arm os_prefix: linux arch: aarch64 + binary: processing*.snap - os: ubuntu-latest os_prefix: linux arch: x64 + binary: processing*.snap - os: windows-latest os_prefix: windows arch: x64 + binary: msi/Processing-*.msi - os: macos-latest os_prefix: macos arch: x64 + binary: dmg/Processing-*.dmg - os: macos-latest os_prefix: macos arch: aarch64 + binary: dmg/Processing-*.dmg steps: + - name: Install Snapcraft + if: runner.os == 'Linux' + uses: samuelmeuli/action-snapcraft@v3 + - name: Install LXD + if: runner.os == 'Linux' + uses: canonical/setup-lxd@main + - name: Checkout Repository uses: actions/checkout@v4 - name: Install Java @@ -43,19 +69,13 @@ jobs: architecture: ${{ matrix.arch }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + - name: Build with Gradle run: ./gradlew packageDistributionForCurrentOS - - name: Add instructions - if: ${{ matrix.os_prefix == 'macos' }} - run: | - echo "run 'xattr -d com.apple.quarantine Processing-${version}.dmg' to remove the quarantine flag" > ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt + - name: Add artifact uses: actions/upload-artifact@v4 with: - name: processing-pr${{ github.event.pull_request.number }}-${{github.sha}}-${{ matrix.os_prefix }}-${{ matrix.arch }}-gradle - path: | - ./app/build/compose/binaries/main/dmg/Processing-*.dmg - ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt - ./app/build/compose/binaries/main/msi/Processing-*.msi - ./app/build/compose/binaries/main/deb/processing*.deb + name: processing-${{ matrix.os_prefix }}-${{ matrix.arch }}-pr_${{ github.ref_name }} retention-days: 5 + path: app/build/compose/binaries/main/${{ matrix.binary }} \ No newline at end of file diff --git a/.github/workflows/release-gradle.yml b/.github/workflows/release-gradle.yml index fb7700cea..638757b0c 100644 --- a/.github/workflows/release-gradle.yml +++ b/.github/workflows/release-gradle.yml @@ -22,7 +22,7 @@ jobs: echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT publish: - name: Publish Processing Core to Maven Central + name: Publish Processing Libraries to Maven Central runs-on: ubuntu-latest needs: version steps: @@ -35,6 +35,7 @@ jobs: java-version: 17 - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + - name: Build with Gradle run: ./gradlew publish env: @@ -50,7 +51,7 @@ jobs: ORG_GRADLE_PROJECT_version: ${{ needs.version.outputs.version }} ORG_GRADLE_PROJECT_group: ${{ vars.PROCESSING_GROUP }} build: - name: Publish Release for ${{ matrix.os_prefix }} (${{ matrix.arch }}) + name: (${{ matrix.os_prefix }}/${{ matrix.arch }}) Create Processing Release runs-on: ${{ matrix.os }} needs: version permissions: @@ -59,26 +60,42 @@ jobs: fail-fast: false matrix: include: - # compiling for arm32 needs a self-hosted runner on Raspi OS (32-bit) - - os: [self-hosted, linux, ARM] + - os: ubuntu-24.04-arm os_prefix: linux - arch: arm + arch: aarch64 + binary: processing_${{ needs.version.outputs.version }}_arm64.snap - os: ubuntu-latest os_prefix: linux arch: x64 + binary: processing_${{ needs.version.outputs.version }}_amd64.snap - os: windows-latest os_prefix: windows arch: x64 + binary: msi/Processing-${{ needs.version.outputs.version }}.msi - os: macos-latest os_prefix: macos arch: x64 + binary: dmg/Processing-${{ needs.version.outputs.version }}.dmg - os: macos-latest os_prefix: macos arch: aarch64 - - os: macos-latest - os_prefix: linux - arch: aarch64 + binary: dmg/Processing-${{ needs.version.outputs.version }}.dmg steps: + - name: Install Certificates for Code Signing + if: runner.os == 'macOS' + continue-on-error: true + uses: apple-actions/import-codesign-certs@v3 + with: + p12-file-base64: ${{ secrets.CERTIFICATES_P12 }} + p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }} + + - name: Install Snapcraft + if: runner.os == 'Linux' + uses: samuelmeuli/action-snapcraft@v3 + - name: Install LXD + if: runner.os == 'Linux' + uses: canonical/setup-lxd@main + - name: Checkout Repository uses: actions/checkout@v4 - name: Install Java @@ -89,12 +106,7 @@ jobs: architecture: ${{ matrix.arch }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - # - name: Install Certificates for Code Signing - # if: ${{ matrix.os_prefix == 'macos' }} - # uses: apple-actions/import-codesign-certs@v3 - # with: - # p12-file-base64: ${{ secrets.CERTIFICATES_P12 }} - # p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }} + - name: Build with Gradle run: ./gradlew packageDistributionForCurrentOS env: @@ -105,9 +117,5 @@ jobs: uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: | - ./app/build/compose/binaries/main/dmg/Processing-*.dmg - ./app/build/compose/binaries/main/dmg/INSTRUCTIONS_FOR_TESTING.txt - ./app/build/compose/binaries/main/msi/Processing-*.msi - ./app/build/compose/binaries/main/deb/processing*.deb - file_glob: true + asset_name: processing-${{ needs.version.outputs.version }}-${{ matrix.os_prefix }}-${{ matrix.arch }} + file: app/build/compose/binaries/main/${{ matrix.binary }} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c1ea1024..d59cb367f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -127,17 +127,33 @@ tasks.register("packageCustomDmg"){ dmg.parentFile.deleteRecursively() dmg.parentFile.mkdirs() + val extra = mutableListOf() + + if(!compose.desktop.application.nativeDistributions.macOS.signing.sign.get()) { + val content = """ + run 'xattr -d com.apple.quarantine Processing-${version}.dmg' to remove the quarantine flag + """.trimIndent() + val instructions = dmg.parentFile.resolve("INSTRUCTIONS.txt") + instructions.writeText(content) + extra.add("--add-file") + extra.add("INSTRUCTIONS.txt") + extra.add(instructions.path) + extra.add("200") + extra.add("25") + } + commandLine("brew", "install", "--quiet", "create-dmg") commandLine("create-dmg", "--volname", packageName, - "--volicon", rootProject.file("build/macos/processing.icns"), + "--volicon", file("macos/volume.icns"), "--background", file("macos/background.png"), "--icon", "$packageName.app", "200", "200", "--window-pos", "200", "200", "--window-size", "775", "485", "--app-drop-link", "500", "200", "--hide-extension", "$packageName.app", + *extra.toTypedArray(), dmg, app ) @@ -153,6 +169,7 @@ tasks.register("packageCustomMsi"){ "dotnet", "build", "/p:Platform=x64", + "/p:Version=$version", "/p:DefineConstants=\"Version=$version;\"" ) } diff --git a/app/macos/volume.icns b/app/macos/volume.icns new file mode 100644 index 0000000000000000000000000000000000000000..cb42dad1704fa032679e33e7b7295cf2eed1c0bf GIT binary patch literal 174924 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 YIC|mcGf*u91dQMi12R;=Tm Date: Mon, 10 Mar 2025 22:33:55 +0100 Subject: [PATCH 028/160] Add portables [skip ci] --- .github/workflows/release-gradle.yml | 28 ++++++++++++++++++++-------- app/build.gradle.kts | 15 +++++++++++++++ build.gradle.kts | 4 ++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release-gradle.yml b/.github/workflows/release-gradle.yml index 638757b0c..e483e7c75 100644 --- a/.github/workflows/release-gradle.yml +++ b/.github/workflows/release-gradle.yml @@ -63,23 +63,28 @@ jobs: - os: ubuntu-24.04-arm os_prefix: linux arch: aarch64 - binary: processing_${{ needs.version.outputs.version }}_arm64.snap + binary: processing_${{ needs.version.outputs.version }}_arm64 + extension: snap - os: ubuntu-latest os_prefix: linux arch: x64 - binary: processing_${{ needs.version.outputs.version }}_amd64.snap + binary: processing_${{ needs.version.outputs.version }}_amd64 + extension: snap - os: windows-latest os_prefix: windows arch: x64 - binary: msi/Processing-${{ needs.version.outputs.version }}.msi + binary: msi/Processing-${{ needs.version.outputs.version }} + extension: msi - os: macos-latest os_prefix: macos arch: x64 - binary: dmg/Processing-${{ needs.version.outputs.version }}.dmg + binary: dmg/Processing-${{ needs.version.outputs.version }} + extension: dmg - os: macos-latest os_prefix: macos arch: aarch64 - binary: dmg/Processing-${{ needs.version.outputs.version }}.dmg + binary: dmg/Processing-${{ needs.version.outputs.version }} + extension: dmg steps: - name: Install Certificates for Code Signing if: runner.os == 'macOS' @@ -113,9 +118,16 @@ jobs: ORG_GRADLE_PROJECT_version: ${{ needs.version.outputs.version }} ORG_GRADLE_PROJECT_group: ${{ vars.PROCESSING_GROUP }} - - name: Upload binaries to release + - name: Upload portables to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - asset_name: processing-${{ needs.version.outputs.version }}-${{ matrix.os_prefix }}-${{ matrix.arch }} - file: app/build/compose/binaries/main/${{ matrix.binary }} \ No newline at end of file + asset_name: processing-${{ needs.version.outputs.version }}-${{ matrix.os_prefix }}-${{ matrix.arch }}-portable.zip + file: app/build/compose/binaries/main/Processing-${{ needs.version.outputs.version }}.zip + + - name: Upload installers to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + asset_name: processing-${{ needs.version.outputs.version }}-${{ matrix.os_prefix }}-${{ matrix.arch }}.${{ matrix.extension }} + file: app/build/compose/binaries/main/${{ matrix.binary }}.${{ matrix.extension }} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d59cb367f..d3a5ac438 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -229,8 +229,23 @@ tasks.register("packageSnap"){ commandLine("snapcraft") } +tasks.register("zipDistributable"){ + dependsOn("createDistributable") + group = "compose desktop" + + val distributable = tasks.named("createDistributable").get() + val dir = distributable.destinationDir.get() + val packageName = distributable.packageName.get() + + from(dir) + archiveBaseName.set(packageName) + destinationDirectory.set(dir.file("../").asFile) +} afterEvaluate{ + tasks.named("createDistributable").configure{ + finalizedBy("zipDistributable") + } tasks.named("packageDmg").configure{ dependsOn("packageCustomDmg") group = "compose desktop" diff --git a/build.gradle.kts b/build.gradle.kts index 44805d0b1..54b998e35 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ -group = "org.processing" -version = "4.4.0" +group = project.findProperty("group") ?: "org.processing" +version = project.findProperty("version") ?: "4.4.0" plugins { kotlin("jvm") version libs.versions.kotlin apply false From 02d0775b50eaebc0a6e21b8635828286076aa90a Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 23:19:27 +0100 Subject: [PATCH 029/160] Add static reference [skip ci] --- .github/workflows/release-gradle.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/release-gradle.yml b/.github/workflows/release-gradle.yml index e483e7c75..9075f7e1e 100644 --- a/.github/workflows/release-gradle.yml +++ b/.github/workflows/release-gradle.yml @@ -21,6 +21,32 @@ jobs: # Set outputs for use in later jobs or steps echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT + reference: + name: Publish Processing Reference to release + runs-on: ubuntu-latest + needs: version + steps: + - name: Checkout Website Repository + uses: actions/checkout@v4 + with: + repository: processing/processing-website + - name: Use Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Make reference.zip + run: npm run zip + - name: Upload reference to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + asset_name: processing-${{ needs.version.outputs.version }}-reference.zip + file: reference.zip + publish: name: Publish Processing Libraries to Maven Central runs-on: ubuntu-latest From 006e1b1a5d55c467c571540f8e4481b33409ce74 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 10 Mar 2025 23:45:27 +0100 Subject: [PATCH 030/160] Portables Fix [skip ci] --- 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 d3a5ac438..53f22130b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -237,7 +237,7 @@ tasks.register("zipDistributable"){ val dir = distributable.destinationDir.get() val packageName = distributable.packageName.get() - from(dir) + from(dir){ eachFile{ permissions{ unix("755") } } } archiveBaseName.set(packageName) destinationDirectory.set(dir.file("../").asFile) } From 60b5d85bce84a3a724b27c966c7b4f80b53930ed Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 11 Mar 2025 00:06:31 +0100 Subject: [PATCH 031/160] General fixes [skip ci] --- .idea/runConfigurations/Processing.xml | 1 - app/build.gradle.kts | 6 ++++-- app/windows/Processing.wxs | 2 +- build.gradle.kts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.idea/runConfigurations/Processing.xml b/.idea/runConfigurations/Processing.xml index 0c7c99651..648638250 100644 --- a/.idea/runConfigurations/Processing.xml +++ b/.idea/runConfigurations/Processing.xml @@ -4,7 +4,6 @@