Skip to content
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import org.gradle.internal.jvm.Jvm
import org.gradle.internal.os.OperatingSystem
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask
import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download
Expand Down Expand Up @@ -119,6 +120,8 @@ dependencies {
implementation(libs.markdown)
implementation(libs.markdownJVM)

@OptIn(ExperimentalComposeLibrary::class)
testImplementation(compose.uiTest)
testImplementation(kotlin("test"))
testImplementation(libs.mockitoKotlin)
testImplementation(libs.junitJupiter)
Expand Down
1 change: 1 addition & 0 deletions app/src/processing/app/ui/Editor.java
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,7 @@ public void buildDevelopMenu(){
var updateTrigger = new JMenuItem(Language.text("menu.develop.check_for_updates"));
updateTrigger.addActionListener(e -> {
Preferences.unset("update.last");
Preferences.setInteger("update.beta_welcome", 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

new UpdateCheck(base);
});
developMenu.add(updateTrigger);
Expand Down
57 changes: 12 additions & 45 deletions app/src/processing/app/ui/WelcomeToBeta.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import processing.app.Base.getVersionName
import processing.app.ui.theme.LocalLocale
import processing.app.ui.theme.LocalTheme
import processing.app.ui.theme.Locale
import processing.app.ui.theme.PDEComposeWindow
import processing.app.ui.theme.PDESwingWindow
import processing.app.ui.theme.ProcessingTheme
import java.awt.Cursor
import java.awt.Dimension
Expand All @@ -54,46 +56,20 @@ import javax.swing.SwingUtilities

class WelcomeToBeta {
companion object{
val windowSize = Dimension(400, 200)
val windowTitle = Locale()["beta.window.title"]

@JvmStatic
fun showWelcomeToBeta() {
val mac = SystemInfo.isMacFullWindowContentSupported
SwingUtilities.invokeLater {
JFrame(windowTitle).apply {
val close = {
Preferences.set("update.beta_welcome", getRevision().toString())
dispose()
}
rootPane.putClientProperty("apple.awt.transparentTitleBar", mac)
rootPane.putClientProperty("apple.awt.fullWindowContent", mac)
defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE
contentPane.add(ComposePanel().apply {
size = windowSize
setContent {
ProcessingTheme {
Box(modifier = Modifier.padding(top = if (mac) 22.dp else 0.dp)) {
welcomeToBeta(close)
}
}
}
})
pack()
background = java.awt.Color.white
setLocationRelativeTo(null)
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
if (e.keyCode == KeyEvent.VK_ESCAPE) close()
}
})
isResizable = false
isVisible = true
requestFocus()
val close = {
Preferences.set("update.beta_welcome", getRevision().toString())
}

PDESwingWindow("beta.window.title", onClose = close) {
welcomeToBeta(close)
}
}
}

val windowSize = Dimension(400, 200)
@Composable
fun welcomeToBeta(close: () -> Unit = {}) {
Row(
Expand Down Expand Up @@ -194,18 +170,9 @@ class WelcomeToBeta {
@JvmStatic
fun main(args: Array<String>) {
application {
val windowState = rememberWindowState(
size = DpSize.Unspecified,
position = WindowPosition(Alignment.Center)
)

Window(onCloseRequest = ::exitApplication, state = windowState, title = windowTitle) {
ProcessingTheme {
Surface(color = colors.background) {
welcomeToBeta {
exitApplication()
}
}
PDEComposeWindow(titleKey = "beta.window.title", onClose = ::exitApplication){
welcomeToBeta {
exitApplication()
}
}
}
Expand Down
129 changes: 107 additions & 22 deletions app/src/processing/app/ui/theme/Locale.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
package processing.app.ui.theme

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import processing.app.LocalPreferences
import processing.app.Messages
import processing.app.Platform
import processing.app.PlatformStart
import processing.app.watchFile
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import processing.app.*
import java.io.File
import java.io.InputStream
import java.util.*

class Locale(language: String = "") : Properties() {
/**
* The Locale class extends the standard Java Properties class
* to provide localization capabilities.
* It loads localization resources from property files based on the specified language code.
* The class also provides a method to change the current locale and update the application accordingly.
* Usage:
* ```
* val locale = Locale("es") { newLocale ->
* // Handle locale change, e.g., update UI or restart application
* }
* val localizedString = locale["someKey"]
* ```
*/
class Locale(language: String = "", val setLocale: ((java.util.Locale) -> Unit)? = null) : Properties() {
var locale: java.util.Locale = java.util.Locale.getDefault()

init {
val locale = java.util.Locale.getDefault()
load(ClassLoader.getSystemResourceAsStream("PDE.properties"))
load(ClassLoader.getSystemResourceAsStream("PDE_${locale.language}.properties") ?: InputStream.nullInputStream())
load(ClassLoader.getSystemResourceAsStream("PDE_${locale.toLanguageTag()}.properties") ?: InputStream.nullInputStream())
load(ClassLoader.getSystemResourceAsStream("PDE_${language}.properties") ?: InputStream.nullInputStream())
loadResourceUTF8("PDE.properties")
loadResourceUTF8("PDE_${locale.language}.properties")
loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties")
loadResourceUTF8("PDE_${language}.properties")
}

fun loadResourceUTF8(path: String) {
val stream = ClassLoader.getSystemResourceAsStream(path)
stream?.reader(charset = Charsets.UTF_8)?.use { reader ->
load(reader)
}
}

@Deprecated("Use get instead", ReplaceWith("get(key)"))
Expand All @@ -28,18 +45,86 @@ class Locale(language: String = "") : Properties() {
return value
}
operator fun get(key: String): String = getProperty(key, key)
fun set(locale: java.util.Locale) {
setLocale?.invoke(locale)
}
}
val LocalLocale = compositionLocalOf { Locale() }
/**
* A CompositionLocal to provide access to the Locale instance
* throughout the composable hierarchy. see [LocaleProvider]
* Usage:
* ```
* val locale = LocalLocale.current
* val localizedString = locale["someKey"]
* ```
*/
val LocalLocale = compositionLocalOf<Locale> { error("No Locale Set") }

/**
* This composable function sets up a locale provider that manages application localization.
* It initializes the locale from a language file, watches for changes to that file, and updates
* the locale accordingly. It uses a [Locale] class to handle loading of localized resources.
*
* Usage:
* ```
* LocaleProvider {
* // Your app content here
* }
* ```
*
* To access the locale:
* ```
* val locale = LocalLocale.current
* val localizedString = locale["someKey"]
* ```
*
* To change the locale:
* ```
* locale.set(java.util.Locale("es"))
* ```
* This will update the `language.txt` file and reload the locale.
*/
@Composable
fun LocaleProvider(content: @Composable () -> Unit) {
PlatformStart()
val preferencesFolderOverride: File? = System.getProperty("processing.app.preferences.folder")?.let { File(it) }

val settingsFolder = preferencesFolderOverride ?: remember{
Platform.init()
Platform.getSettingsFolder()
}
val languageFile = settingsFolder.resolve("language.txt")
remember(languageFile){
if(languageFile.exists()) return@remember

val settingsFolder = Platform.getSettingsFolder()
val languageFile = File(settingsFolder, "language.txt")
watchFile(languageFile)
Messages.log("Creating language file at ${languageFile.absolutePath}")
settingsFolder.mkdirs()
languageFile.writeText(java.util.Locale.getDefault().language)
}

val update = watchFile(languageFile)
var code by remember(languageFile, update){ mutableStateOf(languageFile.readText().substring(0, 2)) }
remember(code) {
val locale = java.util.Locale(code)
java.util.Locale.setDefault(locale)
}

fun setLocale(locale: java.util.Locale) {
Messages.log("Setting locale to ${locale.language}")
languageFile.writeText(locale.language)
code = locale.language
}


val locale = Locale(code, ::setLocale)
remember(code) { Messages.log("Loaded Locale: $code") }
val dir = when(locale["locale.direction"]) {
"rtl" -> LayoutDirection.Rtl
else -> LayoutDirection.Ltr
}

val locale = Locale(languageFile.readText().substring(0, 2))
CompositionLocalProvider(LocalLocale provides locale) {
content()
CompositionLocalProvider(LocalLayoutDirection provides dir) {
CompositionLocalProvider(LocalLocale provides locale) {
content()
}
}
}
Loading