diff --git a/app/src/processing/app/ui/WebFrame.java b/app/ant/processing/app/ui/WebFrame.java
similarity index 100%
rename from app/src/processing/app/ui/WebFrame.java
rename to app/ant/processing/app/ui/WebFrame.java
diff --git a/app/src/processing/app/ui/Welcome.java b/app/ant/processing/app/ui/Welcome.java
similarity index 100%
rename from app/src/processing/app/ui/Welcome.java
rename to app/ant/processing/app/ui/Welcome.java
diff --git a/app/src/main/resources/icons/Discord.svg b/app/src/main/resources/icons/Discord.svg
new file mode 100644
index 0000000000..54f918b869
--- /dev/null
+++ b/app/src/main/resources/icons/Discord.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/src/main/resources/icons/GitHub.svg b/app/src/main/resources/icons/GitHub.svg
new file mode 100644
index 0000000000..39b263b230
--- /dev/null
+++ b/app/src/main/resources/icons/GitHub.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/app/src/main/resources/icons/Instagram.svg b/app/src/main/resources/icons/Instagram.svg
new file mode 100644
index 0000000000..abb51a22e5
--- /dev/null
+++ b/app/src/main/resources/icons/Instagram.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index a1604ff0a0..87bfbd7715 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -23,28 +23,29 @@
package processing.app;
-import java.awt.*;
-import java.awt.event.ActionListener;
-import java.io.*;
-import java.lang.reflect.InvocationTargetException;
-import java.util.*;
-import java.util.List;
-import java.util.Map.Entry;
-
-import javax.swing.*;
-import javax.swing.tree.DefaultMutableTreeNode;
-
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import processing.app.contrib.*;
import processing.app.tools.Tool;
import processing.app.ui.*;
-import processing.app.ui.PreferencesKt;
import processing.app.ui.Toolkit;
-import processing.core.*;
+import processing.core.PApplet;
import processing.data.StringList;
+import javax.swing.*;
+import javax.swing.tree.DefaultMutableTreeNode;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+import java.util.List;
+import java.util.Map.Entry;
+
/**
* The base class for the main processing application.
* Primary role of this class is for platform identification and
@@ -375,13 +376,7 @@ static private void handleWelcomeScreen(Base base) {
// Needs to be shown after the first editor window opens, so that it
// shows up on top, and doesn't prevent an editor window from opening.
if (Preferences.getBoolean("welcome.four.show")) {
- try {
- new Welcome(base);
- } catch (IOException e) {
- Messages.showTrace("Unwelcoming",
- "Please report this error to\n" +
- "https://github.com/processing/processing4/issues", e, false);
- }
+ PDEWelcomeKt.showWelcomeScreen(base);
}
}
@@ -609,7 +604,7 @@ public JMenu initDefaultFileMenu() {
defaultFileMenu.add(item);
item = Toolkit.newJMenuItemShift(Language.text("menu.file.examples"), 'O');
- item.addActionListener(e -> thinkDifferentExamples());
+ item.addActionListener(e -> showExamplesFrame());
defaultFileMenu.add(item);
return defaultFileMenu;
@@ -1885,7 +1880,7 @@ public void handleRestart() {
// }
- public void thinkDifferentExamples() {
+ public void showExamplesFrame() {
nextMode.showExamplesFrame();
}
@@ -2191,7 +2186,10 @@ static private Mode findSketchMode(File folder, List modeList) {
* Show the Preferences window.
*/
public void handlePrefs() {
- PreferencesKt.show();
+ if (preferencesFrame == null) {
+ preferencesFrame = new PreferencesFrame(this);
+ }
+ preferencesFrame.showFrame();
}
diff --git a/app/src/processing/app/Messages.kt b/app/src/processing/app/Messages.kt
index cae54e6e97..fa9bc54a63 100644
--- a/app/src/processing/app/Messages.kt
+++ b/app/src/processing/app/Messages.kt
@@ -18,13 +18,27 @@
*/
package processing.app
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import androidx.compose.ui.window.rememberWindowState
+import com.formdev.flatlaf.FlatLightLaf
import processing.app.ui.Toolkit
+import processing.app.ui.theme.PDETheme
import java.awt.EventQueue
import java.awt.Frame
import java.io.PrintWriter
import java.io.StringWriter
import javax.swing.JFrame
import javax.swing.JOptionPane
+import javax.swing.UIManager
+
class Messages {
companion object {
@@ -270,6 +284,37 @@ class Messages {
}
}
}
+fun main(){
+ val types = mapOf(
+ "message" to { Messages.showMessage("Test Title", "This is a test message.") },
+ "warning" to { Messages.showWarning("Test Warning", "This is a test warning.", Exception("dfdsfjk")) },
+ "trace" to { Messages.showTrace("Test Trace", "This is a test trace.", Exception("Test Exception"), false) },
+ "tiered_warning" to { Messages.showWarningTiered("Test Tiered Warning", "Primary message", "Secondary message", null) },
+ "yes_no" to { Messages.showYesNoQuestion(null, "Test Yes/No", "Do you want to continue?", "Choose yes or no.") },
+ "custom_question" to { Messages.showCustomQuestion(null, "Test Custom Question", "Choose an option:", "Select one of the options below.", 1, "Option 1", "Option 2", "Option 3") },
+ "error" to { Messages.showError("Test Error", "This is a test error.", null) },
+ )
+ Platform.init()
+ UIManager.setLookAndFeel(FlatLightLaf())
+ application {
+ val state = rememberWindowState(
+ size = DpSize(500.dp, 300.dp)
+ )
+ Window(state = state, onCloseRequest = ::exitApplication, title = "Test Messages") {
+ PDETheme {
+ Column {
+ for ((type, action) in types) {
+ Button(onClick = { action() }, modifier = Modifier.padding(8.dp)) {
+ Text("Show $type dialog")
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+}
// Helper functions to give the base classes a color
fun String.formatClassName() = this
diff --git a/app/src/processing/app/api/Contributions.kt b/app/src/processing/app/api/Contributions.kt
index 25e693404b..7b35a30593 100644
--- a/app/src/processing/app/api/Contributions.kt
+++ b/app/src/processing/app/api/Contributions.kt
@@ -28,8 +28,6 @@ class Contributions: SuspendingCliktCommand(){
}
class ExamplesList: SuspendingCliktCommand("list") {
-
-
val serializer = Json {
prettyPrint = true
}
@@ -37,107 +35,121 @@ class Contributions: SuspendingCliktCommand(){
override fun help(context: Context) = "List all examples"
override suspend fun run() {
Platform.init()
- // TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now
- // TODO: Allow the user to change the sketchbook location
- // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode
- val sketchbookFolder = Platform.getDefaultSketchbookFolder()
- val resourcesDir = System.getProperty("compose.application.resources.dir")
-
- val javaMode = "$resourcesDir/modes/java"
-
- val javaModeExamples = File("$javaMode/examples")
- .listFiles()
- ?.map { getSketches(it)}
- ?: emptyList()
-
- val javaModeLibrariesExamples = File("$javaMode/libraries")
- .listFiles{ it.isDirectory }
- ?.map { library ->
- val properties = library.resolve("library.properties")
- val name = findNameInProperties(properties) ?: library.name
-
- val libraryExamples = getSketches(library.resolve("examples"))
- Sketch.Companion.Folder(
- type = "folder",
- name = name,
- path = library.absolutePath,
- mode = "java",
- children = libraryExamples?.children ?: emptyList(),
- sketches = libraryExamples?.sketches ?: emptyList()
- )
- } ?: emptyList()
- val javaModeLibraries = Sketch.Companion.Folder(
- type = "folder",
- name = "Libraries",
- path = "$javaMode/libraries",
- mode = "java",
- children = javaModeLibrariesExamples,
- sketches = emptyList()
- )
-
- val contributedLibraries = sketchbookFolder.resolve("libraries")
- .listFiles{ it.isDirectory }
- ?.map { library ->
- val properties = library.resolve("library.properties")
- val name = findNameInProperties(properties) ?: library.name
- // Get library name from library.properties if it exists
- val libraryExamples = getSketches(library.resolve("examples"))
- Sketch.Companion.Folder(
- type = "folder",
- name = name,
- path = library.absolutePath,
- mode = "java",
- children = libraryExamples?.children ?: emptyList(),
- sketches = libraryExamples?.sketches ?: emptyList()
- )
- } ?: emptyList()
-
- val contributedLibrariesFolder = Sketch.Companion.Folder(
- type = "folder",
- name = "Contributed Libraries",
- path = sketchbookFolder.resolve("libraries").absolutePath,
- mode = "java",
- children = contributedLibraries,
- sketches = emptyList()
- )
-
- val contributedExamples = sketchbookFolder.resolve("examples")
- .listFiles{ it.isDirectory }
- ?.map {
- val properties = it.resolve("examples.properties")
- val name = findNameInProperties(properties) ?: it.name
-
- val sketches = getSketches(it.resolve("examples"))
- Sketch.Companion.Folder(
- type = "folder",
- name,
- path = it.absolutePath,
- mode = "java",
- children = sketches?.children ?: emptyList(),
- sketches = sketches?.sketches ?: emptyList(),
- )
- }
- ?: emptyList()
- val contributedExamplesFolder = Sketch.Companion.Folder(
- type = "folder",
- name = "Contributed Examples",
- path = sketchbookFolder.resolve("examples").absolutePath,
- mode = "java",
- children = contributedExamples,
- sketches = emptyList()
- )
-
- val json = serializer.encodeToString(javaModeExamples + javaModeLibraries + contributedLibrariesFolder + contributedExamplesFolder)
+
+ val json = serializer.encodeToString(listAllExamples())
println(json)
}
- private fun findNameInProperties(properties: File): String? {
- if (!properties.exists()) return null
+ companion object {
+ /**
+ * Get all example sketch folders
+ * @return List of example sketch folders
+ */
+ fun listAllExamples(): List {
+ // TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now
+ // TODO: Allow the user to change the sketchbook location
+ // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode
+ // TODO: Make non-blocking
+ // TODO: Add tests
+
+ val sketchbookFolder = Platform.getDefaultSketchbookFolder()
+ val resourcesDir = System.getProperty("compose.application.resources.dir")
+
+ val javaMode = "$resourcesDir/modes/java"
+
+ val javaModeExamples = File("$javaMode/examples")
+ .listFiles()
+ ?.map { getSketches(it) }
+ ?: emptyList()
+
+ val javaModeLibrariesExamples = File("$javaMode/libraries")
+ .listFiles { it.isDirectory }
+ ?.map { library ->
+ val properties = library.resolve("library.properties")
+ val name = findNameInProperties(properties) ?: library.name
+
+ val libraryExamples = getSketches(library.resolve("examples"))
+ Sketch.Companion.Folder(
+ type = "folder",
+ name = name,
+ path = library.absolutePath,
+ mode = "java",
+ children = libraryExamples?.children ?: emptyList(),
+ sketches = libraryExamples?.sketches ?: emptyList()
+ )
+ } ?: emptyList()
+ val javaModeLibraries = Sketch.Companion.Folder(
+ type = "folder",
+ name = "Libraries",
+ path = "$javaMode/libraries",
+ mode = "java",
+ children = javaModeLibrariesExamples,
+ sketches = emptyList()
+ )
+
+ val contributedLibraries = sketchbookFolder.resolve("libraries")
+ .listFiles { it.isDirectory }
+ ?.map { library ->
+ val properties = library.resolve("library.properties")
+ val name = findNameInProperties(properties) ?: library.name
+ // Get library name from library.properties if it exists
+ val libraryExamples = getSketches(library.resolve("examples"))
+ Sketch.Companion.Folder(
+ type = "folder",
+ name = name,
+ path = library.absolutePath,
+ mode = "java",
+ children = libraryExamples?.children ?: emptyList(),
+ sketches = libraryExamples?.sketches ?: emptyList()
+ )
+ } ?: emptyList()
+
+ val contributedLibrariesFolder = Sketch.Companion.Folder(
+ type = "folder",
+ name = "Contributed Libraries",
+ path = sketchbookFolder.resolve("libraries").absolutePath,
+ mode = "java",
+ children = contributedLibraries,
+ sketches = emptyList()
+ )
+
+ val contributedExamples = sketchbookFolder.resolve("examples")
+ .listFiles { it.isDirectory }
+ ?.map {
+ val properties = it.resolve("examples.properties")
+ val name = findNameInProperties(properties) ?: it.name
+
+ val sketches = getSketches(it.resolve("examples"))
+ Sketch.Companion.Folder(
+ type = "folder",
+ name,
+ path = it.absolutePath,
+ mode = "java",
+ children = sketches?.children ?: emptyList(),
+ sketches = sketches?.sketches ?: emptyList(),
+ )
+ }
+ ?: emptyList()
+ val contributedExamplesFolder = Sketch.Companion.Folder(
+ type = "folder",
+ name = "Contributed Examples",
+ path = sketchbookFolder.resolve("examples").absolutePath,
+ mode = "java",
+ children = contributedExamples,
+ sketches = emptyList()
+ )
+
+ return javaModeExamples + javaModeLibraries + contributedLibrariesFolder + contributedExamplesFolder
+ }
+
+ private fun findNameInProperties(properties: File): String? {
+ if (!properties.exists()) return null
- return properties.readLines().firstNotNullOfOrNull { line ->
- line.split("=", limit = 2)
- .takeIf { it.size == 2 && it[0].trim() == "name" }
- ?.let { it[1].trim() }
+ return properties.readLines().firstNotNullOfOrNull { line ->
+ line.split("=", limit = 2)
+ .takeIf { it.size == 2 && it[0].trim() == "name" }
+ ?.let { it[1].trim() }
+ }
}
}
}
diff --git a/app/src/processing/app/ui/PDEWelcome.kt b/app/src/processing/app/ui/PDEWelcome.kt
new file mode 100644
index 0000000000..f097efbb6c
--- /dev/null
+++ b/app/src/processing/app/ui/PDEWelcome.kt
@@ -0,0 +1,610 @@
+package processing.app.ui
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.EaseInOut
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.slideIn
+import androidx.compose.animation.slideOut
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.NoteAdd
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.icons.filled.Language
+import androidx.compose.material.icons.outlined.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.decodeToImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.application
+import processing.app.*
+import processing.app.api.Contributions.ExamplesList.Companion.listAllExamples
+import processing.app.api.Sketch.Companion.Sketch
+import processing.app.ui.theme.*
+import java.io.File
+import kotlin.io.path.Path
+import kotlin.io.path.exists
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun PDEWelcome(base: Base? = null) {
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.surfaceContainerLowest),
+ ){
+ val shape = RoundedCornerShape(12.dp)
+ val xsPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)
+ val xsModifier = Modifier
+ .defaultMinSize(minHeight = 1.dp)
+ .height(32.dp)
+ val textColor = if(isSystemInDarkTheme()) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSecondaryContainer
+ val locale = LocalLocale.current
+
+ /**
+ * Left main column
+ */
+ Column(
+ verticalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxHeight()
+ .weight(0.8f)
+ .padding(
+ top = 48.dp,
+ start = 56.dp,
+ end = 64.dp,
+ bottom = 56.dp
+ )
+ ) {
+ /**
+ * Title row
+ */
+ Row (
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier.fillMaxWidth()
+ ){
+ Image(
+ painter = painterResource("logo.svg"),
+ modifier = Modifier
+ .size(50.dp),
+ contentDescription = locale["welcome.processing.logo"]
+ )
+ Text(
+ text = locale["welcome.processing.title"],
+ style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
+ color = textColor,
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ )
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.CenterVertically),
+ horizontalArrangement = Arrangement.End,
+ ){
+ val showLanguageMenu = remember { mutableStateOf(false) }
+ OutlinedButton(
+ onClick = {
+ showLanguageMenu.value = !showLanguageMenu.value
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ shape = shape
+ ){
+ Icon(Icons.Default.Language, contentDescription = "", modifier = Modifier.size(20.dp))
+ Spacer(Modifier.width(4.dp))
+ Text(text = locale.locale.displayName)
+ Icon(Icons.Default.ArrowDropDown, contentDescription = "", modifier = Modifier.size(20.dp))
+ languagesDropdown(showLanguageMenu)
+ }
+ }
+ }
+ /**
+ * New sketch, examples, sketchbook card
+ */
+ val colors = ButtonDefaults.textButtonColors(
+ contentColor = textColor
+ )
+ Column{
+ ProvideTextStyle(MaterialTheme.typography.titleMedium) {
+ val medModifier = Modifier
+ .sizeIn(minHeight = 56.dp)
+ TextButton(
+ onClick = {
+ base?.handleNew() ?: noBaseWarning()
+ },
+ colors = colors,
+ modifier = medModifier,
+ shape = shape
+ ) {
+ Icon(Icons.AutoMirrored.Outlined.NoteAdd, contentDescription = "")
+ Spacer(Modifier.width(12.dp))
+ Text(locale["welcome.actions.sketch.new"])
+ }
+ TextButton(
+ onClick = {
+ base?.let{
+ base.showExamplesFrame()
+ } ?: noBaseWarning()
+ },
+ colors = colors,
+ modifier = medModifier,
+ shape = shape
+ ) {
+ Icon(Icons.Outlined.FolderSpecial, contentDescription = "")
+ Spacer(Modifier.width(12.dp))
+ Text(locale["welcome.actions.examples"] )
+ }
+ TextButton(
+ onClick = {
+ base?.let{
+ base.showSketchbookFrame()
+ } ?: noBaseWarning()
+ },
+ colors = colors,
+ modifier = medModifier,
+ shape = shape
+ ) {
+ Icon(Icons.Outlined.FolderOpen, contentDescription = "")
+ Spacer(Modifier.width(12.dp))
+ Text(locale["sketchbook"], modifier = Modifier.align(Alignment.CenterVertically))
+ }
+ }
+ }
+ /**
+ * Resources and community card
+ */
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ ){
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(48.dp),
+ modifier = Modifier
+ .padding(
+ top = 18.dp,
+ end = 24.dp,
+ bottom = 24.dp,
+ start = 24.dp
+ )
+ ) {
+ val colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ ProvideTextStyle(MaterialTheme.typography.labelLarge) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ Text(
+ text = locale["welcome.resources.title"],
+ style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
+ modifier = Modifier.padding(start = 8.dp)
+ )
+ TextButton(
+ onClick = {
+ Platform.openURL("https://processing.org/tutorials/gettingstarted")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(Icons.Outlined.PinDrop, contentDescription = "", modifier = Modifier.size(20.dp))
+ Spacer(Modifier.width(4.dp))
+ Text(
+ text = locale["welcome.resources.get_started"],
+ )
+ }
+ TextButton(
+ onClick = {
+ Platform.openURL("https://processing.org/tutorials")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(Icons.Outlined.School, contentDescription = "", modifier = Modifier.size(20.dp))
+ Spacer(Modifier.width(4.dp))
+ Text(
+ text = locale["welcome.resources.tutorials"],
+ )
+ }
+ TextButton(
+ onClick = {
+ Platform.openURL("https://processing.org/reference")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(Icons.Outlined.Book, contentDescription = "", modifier = Modifier.size(20.dp))
+ Spacer(Modifier.width(4.dp))
+ Text(
+ text = locale["welcome.resources.documentation"],
+ )
+ }
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ Text(
+ text = locale["welcome.community.title"],
+ style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
+ modifier = Modifier.padding(start = 8.dp)
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(48.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ TextButton(
+ onClick = {
+ Platform.openURL("https://discourse.processing.org")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(
+ Icons.Outlined.ChatBubbleOutline,
+ contentDescription = "",
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(Modifier.width(4.dp))
+ Text(
+ text = locale["welcome.community.forum"]
+ )
+ }
+ TextButton(
+ onClick = {
+ Platform.openURL("https://discord.processing.org")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(
+ painterResource("icons/Discord.svg"),
+ contentDescription = "",
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(Modifier.width(4.dp))
+ Text("Discord")
+ }
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ TextButton(
+ onClick = {
+ Platform.openURL("https://github.com/processing/processing4")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(
+ painterResource("icons/GitHub.svg"),
+ contentDescription = "",
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(Modifier.width(4.dp))
+ Text("GitHub")
+ }
+ TextButton(
+ onClick = {
+ Platform.openURL("https://www.instagram.com/processing_core/")
+ },
+ contentPadding = xsPadding,
+ modifier = xsModifier,
+ colors = colors
+ ) {
+ Icon(
+ painterResource("icons/Instagram.svg"),
+ contentDescription = "",
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(Modifier.width(4.dp))
+ Text("Instagram")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ /**
+ * Show on startup checkbox
+ */
+ Row{
+ val preferences = LocalPreferences.current
+ val showOnStartup = preferences["welcome.four.show"].toBoolean()
+ fun toggle(next: Boolean? = null) {
+ preferences["welcome.four.show"] = (next ?: !showOnStartup).toString()
+ }
+ Row(
+ modifier = Modifier
+ .clip(MaterialTheme.shapes.medium)
+ .clickable(onClick = ::toggle)
+ .padding(end = 8.dp)
+ .height(32.dp)
+ ) {
+ Checkbox(
+ checked = showOnStartup,
+ onCheckedChange = ::toggle,
+ colors = CheckboxDefaults.colors(
+ checkedColor = MaterialTheme.colorScheme.tertiary
+ ),
+ modifier = Modifier
+ .defaultMinSize(minHeight = 1.dp)
+ )
+ Text(
+ text = locale["welcome.actions.show_startup"],
+ modifier = Modifier.align(Alignment.CenterVertically),
+ style = MaterialTheme.typography.labelLarge
+ )
+ }
+ }
+ }
+ /**
+ * Examples list
+ */
+ val scrollMargin = 35.dp
+ Column(
+ modifier = Modifier
+ .width(350.dp + scrollMargin)
+ ) {
+ val examples = remember { mutableStateListOf(
+ *listOf(
+ Platform.getContentFile("modes/java/examples/Basics/Arrays/Array"),
+ Platform.getContentFile("modes/java/examples/Basics/Camera/Perspective"),
+ Platform.getContentFile("modes/java/examples/Basics/Color/Brightness"),
+ Platform.getContentFile("modes/java/examples/Basics/Shape/LoadDisplayOBJ")
+ ).map{ Sketch(path = it.absolutePath, name = it.name) }.toTypedArray()
+ )}
+
+ remember {
+ val sketches = mutableListOf()
+ val sketchFolders = listAllExamples()
+ fun gatherSketches(folder: processing.app.api.Sketch.Companion.Folder?) {
+ if (folder == null) return
+ sketches.addAll(folder.sketches.filter { it -> Path(it.path).resolve("${it.name}.png").exists() })
+ folder.children.forEach { child ->
+ gatherSketches(child)
+ }
+ }
+ sketchFolders.forEach { folder ->
+ gatherSketches(folder)
+ }
+ if(sketches.isEmpty()) {
+ return@remember
+ }
+ examples.clear()
+ examples.addAll(sketches.shuffled().take(20))
+ }
+ val state = rememberLazyListState(
+ initialFirstVisibleItemScrollOffset = 150
+ )
+ Box(
+ modifier = Modifier
+ .padding(end = 4.dp)
+ ) {
+ LazyColumn(
+ state = state,
+ contentPadding = PaddingValues(top = 12.dp, bottom = 12.dp, end = 20.dp, start = scrollMargin),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ items(examples) { example ->
+ example.card{
+ base?.let {
+ base.handleOpen("${example.path}/${example.name}.pde")
+ } ?: noBaseWarning()
+ }
+ }
+ }
+ VerticalScrollbar(
+ modifier = Modifier
+ .fillMaxHeight()
+ .align(Alignment.CenterEnd),
+ adapter = rememberScrollbarAdapter(state)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+@OptIn(ExperimentalComposeUiApi::class)
+fun Sketch.card(onOpen: () -> Unit = {}) {
+ val locale = LocalLocale.current
+ val sketch = this
+ var hovered by remember { mutableStateOf(false) }
+ Box(
+ Modifier
+ .border(
+ BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant),
+ shape = MaterialTheme.shapes.medium
+ )
+ .background(
+ MaterialTheme.colorScheme.surfaceVariant,
+ shape = MaterialTheme.shapes.medium
+ )
+ .clip(MaterialTheme.shapes.medium)
+ .fillMaxSize()
+ .aspectRatio(16 / 9f)
+ .onPointerEvent(PointerEventType.Enter) {
+ hovered = true
+ }
+ .onPointerEvent(PointerEventType.Exit) {
+ hovered = false
+ }
+ ) {
+ val image = remember {
+ File(sketch.path, "${sketch.name}.png").takeIf { it.exists() }
+ }
+ if (image == null) {
+ Icon(
+ painter = painterResource("logo.svg"),
+ modifier = Modifier
+ .size(75.dp)
+ .align(Alignment.Center),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
+ contentDescription = "Processing Logo"
+ )
+ HorizontalDivider()
+ } else {
+ val imageBitmap: ImageBitmap = remember(image) {
+ image.inputStream().readAllBytes().decodeToImageBitmap()
+ }
+ Image(
+ painter = BitmapPainter(imageBitmap),
+ modifier = Modifier
+ .fillMaxSize(),
+ contentDescription = sketch.name
+ )
+ }
+ Column(
+ modifier = Modifier.align(Alignment.BottomCenter),
+ ) {
+ val duration = 150
+ AnimatedVisibility(
+ visible = hovered,
+ enter = slideIn(
+ initialOffset = { fullSize -> IntOffset(0, fullSize.height) },
+ animationSpec = tween(
+ durationMillis = duration,
+ easing = EaseInOut
+ )
+ ),
+ exit = slideOut(
+ targetOffset = { fullSize -> IntOffset(0, fullSize.height) },
+ animationSpec = tween(
+ durationMillis = duration,
+ easing = LinearEasing
+ )
+ )
+ ) {
+ Card(
+ modifier = Modifier
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min)
+ .padding(12.dp)
+ .padding(start = 12.dp)
+ ) {
+ Text(
+ text = sketch.name,
+ style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier
+ .padding(8.dp)
+ )
+ Button(
+ onClick = onOpen,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiary,
+ contentColor = MaterialTheme.colorScheme.onTertiary
+ ),
+ contentPadding = PaddingValues(
+ horizontal = 12.dp,
+ vertical = 4.dp
+ ),
+ ) {
+ Text(
+ text = locale["welcome.sketch.open"],
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fun noBaseWarning() {
+ Messages.showWarning(
+ "No Base",
+ "No Base instance provided, this ui is likely being previewed."
+ )
+}
+
+val size = DpSize(970.dp, 600.dp)
+const val titleKey = "menu.help.welcome"
+class WelcomeScreen
+
+fun showWelcomeScreen(base: Base? = null) {
+ PDESwingWindow(
+ titleKey = titleKey,
+ size = size.toDimension(),
+ unique = WelcomeScreen::class,
+ fullWindowContent = true
+ ) {
+ PDEWelcome(base)
+ }
+}
+
+@Composable
+fun languagesDropdown(showOptions: MutableState) {
+ val locale = LocalLocale.current
+ val languages = if (Preferences.isInitialized()) Language.getLanguages() else mapOf("en" to "English")
+ DropdownMenu(
+ expanded = showOptions.value,
+ onDismissRequest = {
+ showOptions.value = false
+ },
+ ) {
+ languages.forEach { family ->
+ DropdownMenuItem(
+ text = { Text(family.value) },
+ onClick = {
+ locale.set(java.util.Locale(family.key))
+ showOptions.value = false
+ }
+ )
+ }
+ }
+}
+
+fun main(){
+ application {
+ PDEComposeWindow(titleKey = titleKey, size = size, fullWindowContent = true) {
+ PDETheme(darkTheme = true) {
+ PDEWelcome()
+ }
+ }
+ PDEComposeWindow(titleKey = titleKey, size = size, fullWindowContent = true) {
+ PDETheme(darkTheme = false) {
+ PDEWelcome()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/Preferences.kt b/app/src/processing/app/ui/Preferences.kt
deleted file mode 100644
index 7fd9f56350..0000000000
--- a/app/src/processing/app/ui/Preferences.kt
+++ /dev/null
@@ -1,325 +0,0 @@
-package processing.app.ui
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.NavigationRail
-import androidx.compose.material3.NavigationRailItem
-import androidx.compose.material3.SearchBar
-import androidx.compose.material3.SearchBarDefaults
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateMapOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Window
-import androidx.compose.ui.window.WindowPlacement
-import androidx.compose.ui.window.WindowPosition
-import androidx.compose.ui.window.application
-import androidx.compose.ui.window.rememberWindowState
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.debounce
-import processing.app.LocalPreferences
-import processing.app.ui.PDEPreferences.Companion.preferences
-import processing.app.ui.preferences.General
-import processing.app.ui.preferences.Interface
-import processing.app.ui.preferences.Other
-import processing.app.ui.theme.LocalLocale
-import processing.app.ui.theme.PDESwingWindow
-import processing.app.ui.theme.PDETheme
-import java.awt.Dimension
-import javax.swing.SwingUtilities
-
-val LocalPreferenceGroups = compositionLocalOf>> {
- error("No Preference Groups Set")
-}
-
-class PDEPreferences {
- companion object{
- val groups = mutableStateMapOf>()
- fun register(preference: PDEPreference) {
- val list = groups[preference.group]?.toMutableList() ?: mutableListOf()
- list.add(preference)
- groups[preference.group] = list
- }
- init{
- General.register()
- Interface.register()
- Other.register()
- }
-
- /**
- * Composable function to display the preferences UI.
- */
- @OptIn(ExperimentalMaterial3Api::class)
- @Composable
- fun preferences(){
- var visible by remember { mutableStateOf(groups) }
- val sortedGroups = remember {
- val keys = visible.keys
- keys.toSortedSet {
- a, b ->
- when {
- a.after == b -> 1
- b.after == a -> -1
- else -> a.name.compareTo(b.name)
- }
- }
- }
- var selected by remember { mutableStateOf(sortedGroups.first()) }
- CompositionLocalProvider(
- LocalPreferenceGroups provides visible
- ) {
- Row {
- NavigationRail(
- header = {
- Text(
- "Settings",
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 42.dp)
- )
-
- },
- modifier = Modifier
- .defaultMinSize(minWidth = 200.dp)
- ) {
-
- for (group in sortedGroups) {
- NavigationRailItem(
- selected = selected == group,
- enabled = visible.keys.contains(group),
- onClick = {
- selected = group
- },
- icon = {
- group.icon()
- },
- label = {
- Text(group.name)
- }
- )
- }
- }
- Box(modifier = Modifier.padding(top = 42.dp)) {
- Column(modifier = Modifier
- .fillMaxSize()
- ) {
- var query by remember { mutableStateOf("") }
- val locale = LocalLocale.current
- LaunchedEffect(query){
-
- snapshotFlow { query }
- .debounce(100)
- .collect{
- if(it.isBlank()){
- visible = groups
- return@collect
- }
- val filtered = mutableStateMapOf>()
- for((group, preferences) in groups){
- val matching = preferences.filter { preference ->
- if(preference.key == "other"){
- return@filter true
- }
- if(preference.key.contains(it, ignoreCase = true)){
- return@filter true
- }
- val description = locale[preference.descriptionKey]
- description.contains(it, ignoreCase = true)
- }
- if(matching.isNotEmpty()){
- filtered[group] = matching
- }
- }
- visible = filtered
- }
-
- }
- SearchBar(
- inputField = {
- SearchBarDefaults.InputField(
- query = query,
- onQueryChange = {
- query = it
- },
- onSearch = {
-
- },
- expanded = false,
- onExpandedChange = { },
- placeholder = { Text("Search") }
- )
- },
- expanded = false,
- onExpandedChange = {},
- modifier = Modifier.align(Alignment.End).padding(16.dp)
- ) {
-
- }
-
- val preferences = visible[selected] ?: emptyList()
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(20.dp)
- ) {
- items(preferences){ preference ->
- preference.showControl()
- }
- }
- }
- }
- }
- }
- }
-
-
-
- @JvmStatic
- fun main(args: Array) {
- application {
- Window(onCloseRequest = ::exitApplication){
- remember{
- window.rootPane.putClientProperty("apple.awt.fullWindowContent", true)
- window.rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
- }
- PDETheme(darkTheme = true) {
- preferences()
- }
- }
- Window(onCloseRequest = ::exitApplication){
- remember{
- window.rootPane.putClientProperty("apple.awt.fullWindowContent", true)
- window.rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
- }
- PDETheme(darkTheme = false) {
- preferences()
- }
- }
- }
- }
- }
-}
-
-/**
- * Data class representing a single preference in the preferences system.
- *
- * Usage:
- * ```
- * PDEPreferences.register(
- * PDEPreference(
- * key = "preference.key",
- * descriptionKey = "preference.description",
- * group = somePreferenceGroup,
- * control = { preference, updatePreference ->
- * // Composable UI to modify the preference
- * }
- * )
- * )
- * ```
- */
-data class PDEPreference(
- /**
- * The key in the preferences file used to store this preference.
- */
- val key: String,
- /**
- * The key for the description of this preference, used for localization.
- */
- val descriptionKey: String,
- /**
- * The group this preference belongs to.
- */
- val group: PDEPreferenceGroup,
- /**
- * A Composable function that defines the control used to modify this preference.
- * It takes the current preference value and a function to update the preference.
- */
- val control: @Composable (preference: String?, updatePreference: (newValue: String) -> Unit) -> Unit = { preference, updatePreference -> },
-
- /**
- * If true, no padding will be applied around this preference's UI.
- */
- val noPadding: Boolean = false,
-)
-
-/**
- * Composable function to display the preference's description and control.
- */
-@Composable
-private fun PDEPreference.showControl() {
- val locale = LocalLocale.current
- val prefs = LocalPreferences.current
- Text(
- text = locale[descriptionKey],
- modifier = Modifier.padding(horizontal = 20.dp),
- style = MaterialTheme.typography.titleMedium
- )
- val show = @Composable {
- control(prefs[key]) { newValue ->
- prefs[key] = newValue
- }
- }
-
- if(noPadding){
- show()
- }else{
- Box(modifier = Modifier.padding(horizontal = 20.dp)) {
- show()
- }
- }
-
-}
-
-/**
- * Data class representing a group of preferences.
- */
-data class PDEPreferenceGroup(
- /**
- * The name of this group.
- */
- val name: String,
- /**
- * The icon representing this group.
- */
- val icon: @Composable () -> Unit,
- /**
- * The group that comes before this one in the list.
- */
- val after: PDEPreferenceGroup? = null,
-)
-
-fun show(){
- SwingUtilities.invokeLater {
- PDESwingWindow(
- titleKey = "preferences",
- fullWindowContent = true,
- size = Dimension(800, 600)
- ) {
- PDETheme {
- preferences()
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/Start.kt b/app/src/processing/app/ui/Start.kt
index 7de371eec4..d7ed635ecf 100644
--- a/app/src/processing/app/ui/Start.kt
+++ b/app/src/processing/app/ui/Start.kt
@@ -46,6 +46,8 @@ class Start {
var visible by remember { mutableStateOf(false) }
val composition = rememberCoroutineScope()
LaunchedEffect(Unit) {
+ Toolkit.setIcon(window)
+
visible = true
composition.launch {
delay(duration.toLong() + timeMargin)
diff --git a/app/src/processing/app/ui/preferences/General.kt b/app/src/processing/app/ui/preferences/General.kt
deleted file mode 100644
index 5f56187f46..0000000000
--- a/app/src/processing/app/ui/preferences/General.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-package processing.app.ui.preferences
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
-import androidx.compose.material3.Button
-import androidx.compose.material3.FilterChip
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import processing.app.Preferences
-import processing.app.SketchName
-import processing.app.ui.PDEPreference
-import processing.app.ui.PDEPreferenceGroup
-import processing.app.ui.PDEPreferences
-
-
-class General {
- companion object{
- val general = PDEPreferenceGroup(
- name = "General",
- icon = {
- Icon(Icons.Default.Settings, contentDescription = "A settings icon")
- }
- )
-
- fun register() {
- PDEPreferences.register(
- PDEPreference(
- key = "sketchbook.path.four",
- descriptionKey = "preferences.sketchbook_location",
- group = general,
- control = { preference, updatePreference ->
- Row (
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier
- .fillMaxWidth()
- ) {
- TextField(
- value = preference ?: "",
- onValueChange = {
- updatePreference(it)
- }
- )
- Button(
- onClick = {
-
- }
- ) {
- Text("Browse")
- }
- }
- }
- )
- )
- PDEPreferences.register(
- PDEPreference(
- key = "sketch.name.approach",
- descriptionKey = "preferences.sketch_naming",
- group = general,
- control = { preference, updatePreference ->
- Row{
- for (option in if(Preferences.isInitialized()) SketchName.getOptions() else arrayOf(
- "timestamp",
- "untitled",
- "custom"
- )) {
- FilterChip(
- selected = preference == option,
- onClick = {
- updatePreference(option)
- },
- label = {
- Text(option)
- },
- modifier = Modifier.padding(4.dp),
- )
- }
- }
- }
- )
- )
- PDEPreferences.register(
- PDEPreference(
- key = "update.check",
- descriptionKey = "preferences.check_for_updates_on_startup",
- group = general,
- control = { preference, updatePreference ->
- Switch(
- checked = preference.toBoolean(),
- onCheckedChange = {
- updatePreference(it.toString())
- }
- )
- }
- )
- )
- PDEPreferences.register(
- PDEPreference(
- key = "welcome.show",
- descriptionKey = "preferences.show_welcome_screen_on_startup",
- group = general,
- control = { preference, updatePreference ->
- Switch(
- checked = preference.toBoolean(),
- onCheckedChange = {
- updatePreference(it.toString())
- }
- )
- }
- )
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/preferences/Interface.kt b/app/src/processing/app/ui/preferences/Interface.kt
deleted file mode 100644
index fc384fbc59..0000000000
--- a/app/src/processing/app/ui/preferences/Interface.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-package processing.app.ui.preferences
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowDropDown
-import androidx.compose.material.icons.filled.TextIncrease
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Slider
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import processing.app.Language
-import processing.app.Preferences
-import processing.app.ui.PDEPreference
-import processing.app.ui.PDEPreferenceGroup
-import processing.app.ui.PDEPreferences
-import processing.app.ui.Toolkit
-import processing.app.ui.preferences.General.Companion.general
-import processing.app.ui.theme.LocalLocale
-import java.util.Locale
-
-class Interface {
- companion object{
- val interfaceAndFonts = PDEPreferenceGroup(
- name = "Interface",
- icon = {
- Icon(Icons.Default.TextIncrease, contentDescription = "Interface")
- },
- after = general
- )
-
- fun register() {
- PDEPreferences.register(PDEPreference(
- key = "language",
- descriptionKey = "preferences.language",
- group = interfaceAndFonts,
- control = { preference, updatePreference ->
- val locale = LocalLocale.current
- var showOptions by remember { mutableStateOf(false) }
- val languages = if(Preferences.isInitialized()) Language.getLanguages() else mapOf("en" to "English")
- TextField(
- value = locale.locale.displayName,
- readOnly = true,
- onValueChange = { },
- trailingIcon = {
- Icon(
- Icons.Default.ArrowDropDown,
- contentDescription = "Select Font Family",
- modifier = Modifier
- .clickable{
- showOptions = true
- }
- )
- }
- )
- DropdownMenu(
- expanded = showOptions,
- onDismissRequest = {
- showOptions = false
- },
- ) {
- languages.forEach { family ->
- DropdownMenuItem(
- text = { Text(family.value) },
- onClick = {
- locale.set(Locale(family.key))
- showOptions = false
- }
- )
- }
- }
- }
- ))
-
- PDEPreferences.register(
- PDEPreference(
- key = "editor.font.family",
- descriptionKey = "preferences.editor_and_console_font",
- group = interfaceAndFonts,
- control = { preference, updatePreference ->
- var showOptions by remember { mutableStateOf(false) }
- val families = if(Preferences.isInitialized()) Toolkit.getMonoFontFamilies() else arrayOf("Monospaced")
- TextField(
- value = preference ?: families.firstOrNull().orEmpty(),
- readOnly = true,
- onValueChange = { updatePreference (it) },
- trailingIcon = {
- Icon(
- Icons.Default.ArrowDropDown,
- contentDescription = "Select Font Family",
- modifier = Modifier
- .clickable{
- showOptions = true
- }
- )
- }
- )
- DropdownMenu(
- expanded = showOptions,
- onDismissRequest = {
- showOptions = false
- },
- ) {
- families.forEach { family ->
- DropdownMenuItem(
- text = { Text(family) },
- onClick = {
- updatePreference(family)
- showOptions = false
- }
- )
- }
-
- }
- }
- )
- )
-
- PDEPreferences.register(PDEPreference(
- key = "editor.font.size",
- descriptionKey = "preferences.editor_font_size",
- group = interfaceAndFonts,
- control = { preference, updatePreference ->
- Column {
- Text(
- text = "${preference ?: "12"} pt",
- modifier = Modifier.width(120.dp)
- )
- Slider(
- value = (preference ?: "12").toFloat(),
- onValueChange = { updatePreference(it.toInt().toString()) },
- valueRange = 10f..48f,
- steps = 18,
- )
- }
- }
- ))
- PDEPreferences.register(PDEPreference(
- key = "console.font.size",
- descriptionKey = "preferences.console_font_size",
- group = interfaceAndFonts,
- control = { preference, updatePreference ->
- Column {
- Text(
- text = "${preference ?: "12"} pt",
- modifier = Modifier.width(120.dp)
- )
- Slider(
- value = (preference ?: "12").toFloat(),
- onValueChange = { updatePreference(it.toInt().toString()) },
- valueRange = 10f..48f,
- steps = 18,
- )
- }
- }
- ))
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/preferences/Other.kt b/app/src/processing/app/ui/preferences/Other.kt
deleted file mode 100644
index f5f65ea9c8..0000000000
--- a/app/src/processing/app/ui/preferences/Other.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package processing.app.ui.preferences
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Map
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import processing.app.LocalPreferences
-import processing.app.ui.LocalPreferenceGroups
-import processing.app.ui.PDEPreference
-import processing.app.ui.PDEPreferenceGroup
-import processing.app.ui.PDEPreferences
-import processing.app.ui.preferences.Interface.Companion.interfaceAndFonts
-import processing.app.ui.theme.LocalLocale
-
-class Other {
- companion object{
- val other = PDEPreferenceGroup(
- name = "Other",
- icon = {
- Icon(Icons.Default.Map, contentDescription = "A map icon")
- },
- after = interfaceAndFonts
- )
- fun register() {
- PDEPreferences.register(
- PDEPreference(
- key = "other",
- descriptionKey = "preferences.other",
- group = other,
- noPadding = true,
- control = { _, _ ->
- val prefs = LocalPreferences.current
- val groups = LocalPreferenceGroups.current
- val restPrefs = remember {
- val keys = prefs.keys.mapNotNull { it as? String }
- val existing = groups.values.flatten().map { it.key }
- keys.filter { it !in existing }.sorted()
- }
- val locale = LocalLocale.current
-
- for(prefKey in restPrefs){
- val value = prefs[prefKey]
- Row (
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier
- .fillMaxWidth()
- .padding(20.dp)
- ){
- Text(
- text = locale[prefKey],
- modifier = Modifier.align(Alignment.CenterVertically)
- )
- TextField(value ?: "", onValueChange = {
- prefs[prefKey] = it
- })
- }
- }
-
- }
- )
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/theme/Colors.kt b/app/src/processing/app/ui/theme/Colors.kt
index 61c6d6b55f..af423ba488 100644
--- a/app/src/processing/app/ui/theme/Colors.kt
+++ b/app/src/processing/app/ui/theme/Colors.kt
@@ -33,54 +33,10 @@ class ProcessingColors{
val foundationDark = Color(0xFF5501a4)
val downloadInactive = Color(0xFF8890B3)
- val downloadBackgroundActive = Color(0x14508BFF)
+ val downloadBackgroundActive = Color(0xFF14508B)
}
}
-@Deprecated("Use PDE3LightColor instead")
-val PDE2LightColors = Colors(
- primary = ProcessingColors.blue,
- primaryVariant = ProcessingColors.lightBlue,
- onPrimary = ProcessingColors.white,
-
- secondary = ProcessingColors.deepBlue,
- secondaryVariant = ProcessingColors.darkBlue,
- onSecondary = ProcessingColors.white,
-
- background = ProcessingColors.white,
- onBackground = ProcessingColors.darkBlue,
-
- surface = ProcessingColors.lightGray,
- onSurface = ProcessingColors.darkerGray,
-
- error = ProcessingColors.error,
- onError = ProcessingColors.white,
-
- isLight = true,
-)
-
-@Deprecated("Use PDE3DarkColor instead")
-val PDE2DarkColors = Colors(
- primary = ProcessingColors.deepBlue,
- primaryVariant = ProcessingColors.darkBlue,
- onPrimary = ProcessingColors.white,
-
- secondary = ProcessingColors.lightBlue,
- secondaryVariant = ProcessingColors.blue,
- onSecondary = ProcessingColors.white,
-
- background = ProcessingColors.veryDarkGray,
- onBackground = ProcessingColors.white,
-
- surface = ProcessingColors.darkerGray,
- onSurface = ProcessingColors.lightGray,
-
- error = ProcessingColors.error,
- onError = ProcessingColors.white,
-
- isLight = false,
-)
-
val PDELightColor = lightColorScheme(
primary = ProcessingColors.blue,
onPrimary = ProcessingColors.white,
diff --git a/app/src/processing/app/ui/theme/Theme.kt b/app/src/processing/app/ui/theme/Theme.kt
index 9e41227ed1..c59c5025cd 100644
--- a/app/src/processing/app/ui/theme/Theme.kt
+++ b/app/src/processing/app/ui/theme/Theme.kt
@@ -1,61 +1,24 @@
package processing.app.ui.theme
-import androidx.compose.foundation.background
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Map
-import androidx.compose.material3.AssistChip
-import androidx.compose.material3.Badge
-import androidx.compose.material3.BadgedBox
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Card
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.FilterChip
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.Text
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.RadioButton
-import androidx.compose.material3.RangeSlider
-import androidx.compose.material3.Slider
-import androidx.compose.material3.Switch
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TextField
-import androidx.compose.material3.TriStateCheckbox
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.state.ToggleableState
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
+import darkScheme
+import lightScheme
import processing.app.PreferencesProvider
/**
@@ -90,13 +53,21 @@ fun PDETheme(
PreferencesProvider {
LocaleProvider {
MaterialTheme(
- colorScheme = if(darkTheme) PDEDarkColor else PDELightColor,
+ colorScheme = if(darkTheme) darkScheme else lightScheme,
typography = PDETypography
){
- Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) {
+ Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.surfaceContainerLowest)) {
CompositionLocalProvider(
- LocalContentColor provides MaterialTheme.colorScheme.onBackground,
- LocalDensity provides Density(1.25f, 1.25f),
+ LocalScrollbarStyle provides ScrollbarStyle(
+ minimalHeight = 16.dp,
+ thickness = 8.dp,
+ shape = MaterialTheme.shapes.extraSmall,
+ hoverDurationMillis = 300,
+ unhoverColor = MaterialTheme.colorScheme.outlineVariant,
+ hoverColor = MaterialTheme.colorScheme.outlineVariant
+ ),
+ LocalContentColor provides MaterialTheme.colorScheme.onSurface,
+// LocalDensity provides Density(1.25f, 1.25f),
content = content
)
}
@@ -137,66 +108,64 @@ fun main() {
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
ComponentPreview("Colors") {
+ val colors = listOf>(
+ Triple("Primary", MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.onPrimary),
+ Triple("Secondary", MaterialTheme.colorScheme.secondary, MaterialTheme.colorScheme.onSecondary),
+ Triple("Tertiary", MaterialTheme.colorScheme.tertiary, MaterialTheme.colorScheme.onTertiary),
+ Triple("Primary Container", MaterialTheme.colorScheme.primaryContainer, MaterialTheme.colorScheme.onPrimaryContainer),
+ Triple("Secondary Container", MaterialTheme.colorScheme.secondaryContainer, MaterialTheme.colorScheme.onSecondaryContainer),
+ Triple("Tertiary Container", MaterialTheme.colorScheme.tertiaryContainer, MaterialTheme.colorScheme.onTertiaryContainer),
+ Triple("Error Container", MaterialTheme.colorScheme.errorContainer, MaterialTheme.colorScheme.onErrorContainer),
+ Triple("Background", MaterialTheme.colorScheme.background, MaterialTheme.colorScheme.onBackground),
+ Triple("Surface", MaterialTheme.colorScheme.surface, MaterialTheme.colorScheme.onSurface),
+ Triple("Surface Variant", MaterialTheme.colorScheme.surfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant),
+ Triple("Error", MaterialTheme.colorScheme.error, MaterialTheme.colorScheme.onError),
+
+ Triple("Surface Lowest", MaterialTheme.colorScheme.surfaceContainerLowest, MaterialTheme.colorScheme.onSurface),
+ Triple("Surface Low", MaterialTheme.colorScheme.surfaceContainerLow, MaterialTheme.colorScheme.onSurface),
+ Triple("Surface", MaterialTheme.colorScheme.surfaceContainer, MaterialTheme.colorScheme.onSurface),
+ Triple("Surface High", MaterialTheme.colorScheme.surfaceContainerHigh, MaterialTheme.colorScheme.onSurface),
+ Triple("Surface Highest", MaterialTheme.colorScheme.surfaceContainerHighest, MaterialTheme.colorScheme.onSurface),
+ )
Column {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
- onClick = {}) {
- Text("Primary", color = MaterialTheme.colorScheme.onPrimary)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary),
- onClick = {}) {
- Text("Secondary", color = MaterialTheme.colorScheme.onSecondary)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiary),
- onClick = {}) {
- Text("Tertiary", color = MaterialTheme.colorScheme.onTertiary)
+ val section = colors.subList(0,3)
+ for((name, color, onColor) in section){
+ Button(
+ colors = ButtonDefaults.buttonColors(containerColor = color),
+ onClick = {}) {
+ Text(name, color = onColor)
+ }
}
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primaryContainer),
- onClick = {}) {
- Text("Primary Container", color = MaterialTheme.colorScheme.onPrimaryContainer)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer),
- onClick = {}) {
- Text("Secondary Container", color = MaterialTheme.colorScheme.onSecondaryContainer)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer),
- onClick = {}) {
- Text("Tertiary Container", color = MaterialTheme.colorScheme.onTertiaryContainer)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.errorContainer),
- onClick = {}) {
- Text("Error Container", color = MaterialTheme.colorScheme.onErrorContainer)
+ val section = colors.subList(3,7)
+ for((name, color, onColor) in section){
+ Button(
+ colors = ButtonDefaults.buttonColors(containerColor = color),
+ onClick = {}) {
+ Text(name, color = onColor)
+ }
}
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background),
- onClick = {}) {
- Text("Background", color = MaterialTheme.colorScheme.onBackground)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surface),
- onClick = {}) {
- Text("Surface", color = MaterialTheme.colorScheme.onSurface)
+ val section = colors.subList(7,11)
+ for((name, color, onColor) in section){
+ Button(
+ colors = ButtonDefaults.buttonColors(containerColor = color),
+ onClick = {}) {
+ Text(name, color = onColor)
+ }
}
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
- onClick = {}) {
- Text("Surface Variant", color = MaterialTheme.colorScheme.onSurfaceVariant)
- }
- Button(
- colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
- onClick = {}) {
- Text("Error", color = MaterialTheme.colorScheme.onError)
+ }
+ Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ val section = colors.subList(11, 16)
+ for ((name, color, onColor) in section) {
+ Button(
+ colors = ButtonDefaults.buttonColors(containerColor = color),
+ onClick = {}) {
+ Text(name, color = onColor)
+ }
}
}
}
@@ -337,10 +306,18 @@ fun main() {
}
+ ComponentPreview("Card") {
+ Card{
+ Text("Hello, Tabs!", modifier = Modifier.padding(20.dp))
+ }
+ }
+
ComponentPreview("Scrollable View") {
}
+
+
ComponentPreview("Tabs") {
}
diff --git a/app/src/processing/app/ui/theme/Window.kt b/app/src/processing/app/ui/theme/Window.kt
index 98a4e00807..f725a999b5 100644
--- a/app/src/processing/app/ui/theme/Window.kt
+++ b/app/src/processing/app/ui/theme/Window.kt
@@ -5,28 +5,26 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.awt.ComposePanel
import androidx.compose.ui.awt.ComposeWindow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
-import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import com.formdev.flatlaf.util.SystemInfo
+import processing.app.ui.Toolkit
import java.awt.Dimension
-import java.awt.event.KeyAdapter
-import java.awt.event.KeyEvent
import javax.swing.JFrame
-import javax.swing.UIManager
+import javax.swing.JRootPane
+import kotlin.reflect.KClass
val LocalWindow = compositionLocalOf { error("No Window Set") }
@@ -48,6 +46,8 @@ val LocalWindow = compositionLocalOf { error("No Window Set") }
* @param size The desired size of the window. If null, the window will use its default size.
* @param minSize The minimum size of the window. If null, no minimum size is set.
* @param maxSize The maximum size of the window. If null, no maximum size is set.
+ * @param unique An optional unique identifier for the window to prevent duplicates.
+ * @param onClose A lambda function to be called when the window is requested to close.
* @param fullWindowContent If true, the content will extend into the title bar area on macOS.
* @param content The composable content to be displayed in the window.
*/
@@ -56,6 +56,7 @@ class PDESwingWindow(
size: Dimension? = null,
minSize: Dimension? = null,
maxSize: Dimension? = null,
+ unique: KClass<*>? = null,
fullWindowContent: Boolean = false,
onClose: () -> Unit = {},
content: @Composable () -> Unit
@@ -75,7 +76,13 @@ class PDESwingWindow(
}
setLocationRelativeTo(null)
setContent {
- PDEWindowContent(window, titleKey, fullWindowContent, content)
+ PDEWindowContent(
+ window = window,
+ titleKey = titleKey,
+ unique = unique,
+ fullWindowContent = fullWindowContent,
+ content = content
+ )
}
window.addWindowStateListener {
if(it.newState == JFrame.DISPOSE_ON_CLOSE){
@@ -87,12 +94,15 @@ class PDESwingWindow(
}
}
+private val windows = mutableMapOf, ComposeWindow>()
+
/**
* Internal Composable function to set up the window content with theming and localization.
* It also handles macOS specific properties for full window content.
*
* @param window The JFrame instance to be configured.
* @param titleKey The key for the window title, which will be localized.
+ * @param unique An optional unique identifier for the window to prevent duplicates.
* @param fullWindowContent If true, the content will extend into the title bar area on macOS.
* @param content The composable content to be displayed in the window.
*/
@@ -100,6 +110,7 @@ class PDESwingWindow(
private fun PDEWindowContent(
window: ComposeWindow,
titleKey: String,
+ unique: KClass<*>? = null,
fullWindowContent: Boolean = false,
content: @Composable () -> Unit
){
@@ -107,6 +118,21 @@ private fun PDEWindowContent(
remember {
window.rootPane.putClientProperty("apple.awt.fullWindowContent", mac && fullWindowContent)
window.rootPane.putClientProperty("apple.awt.transparentTitleBar", mac && fullWindowContent)
+ Toolkit.setIcon(window)
+ }
+ if(unique != null && windows.contains(unique) && windows[unique] != null){
+ windows[unique]?.toFront()
+ window.dispose()
+ return
+ }
+
+ DisposableEffect(unique){
+ unique?.let {
+ windows[it] = window
+ }
+ onDispose {
+ windows.remove(unique)
+ }
}
CompositionLocalProvider(LocalWindow provides window) {
@@ -148,13 +174,10 @@ private fun PDEWindowContent(
* fullscreen if it contains any of [fillMaxWidth]/[fillMaxSize]/[fillMaxHeight] etc.
* @param minSize The minimum size of the window. Defaults to unspecified size which means no minimum size is set.
* @param maxSize The maximum size of the window. Defaults to unspecified size which means no maximum size is set.
- * @param fullWindowContent If true, the content will extend into the title bar area on
- * macOS.
+ * @param unique An optional unique identifier for the window to prevent duplicates.
+ * @param fullWindowContent If true, the content will extend into the title bar area on macOS.
* @param onClose A lambda function to be called when the window is requested to close.
* @param content The composable content to be displayed in the window.
- *
- *
- *
*/
@Composable
fun PDEComposeWindow(
@@ -162,6 +185,7 @@ fun PDEComposeWindow(
size: DpSize = DpSize.Unspecified,
minSize: DpSize = DpSize.Unspecified,
maxSize: DpSize = DpSize.Unspecified,
+ unique: KClass<*>? = null,
fullWindowContent: Boolean = false,
onClose: () -> Unit = {},
content: @Composable () -> Unit
@@ -175,7 +199,13 @@ fun PDEComposeWindow(
window.minimumSize = minSize.toDimension()
window.maximumSize = maxSize.toDimension()
}
- PDEWindowContent(window, titleKey, fullWindowContent, content)
+ PDEWindowContent(
+ window = window,
+ titleKey = titleKey,
+ unique = unique,
+ fullWindowContent = fullWindowContent,
+ content = content
+ )
}
}
diff --git a/app/src/processing/app/ui/theme/m3/Color.kt b/app/src/processing/app/ui/theme/m3/Color.kt
new file mode 100644
index 0000000000..b2047ce7e6
--- /dev/null
+++ b/app/src/processing/app/ui/theme/m3/Color.kt
@@ -0,0 +1,248 @@
+import androidx.compose.ui.graphics.Color
+
+val primaryLight = Color(0xFF525A92)
+val onPrimaryLight = Color(0xFFFFFFFF)
+val primaryContainerLight = Color(0xFF293DAE)
+val onPrimaryContainerLight = Color(0xFFABB5FF)
+val secondaryLight = Color(0xFF555D7D)
+val onSecondaryLight = Color(0xFFFFFFFF)
+val secondaryContainerLight = Color(0xFF8890B3)
+val onSecondaryContainerLight = Color(0xFF212946)
+val tertiaryLight = Color(0xFF0052CC)
+val onTertiaryLight = Color(0xFFFFFFFF)
+val tertiaryContainerLight = Color(0xFF0468FF)
+val onTertiaryContainerLight = Color(0xFFFBF9FF)
+val errorLight = Color(0xFFBB0026)
+val onErrorLight = Color(0xFFFFFFFF)
+val errorContainerLight = Color(0xFFE41D37)
+val onErrorContainerLight = Color(0xFFFFFBFF)
+val backgroundLight = Color(0xFFFBF8FF)
+val onBackgroundLight = Color(0xFF1A1B22)
+val surfaceLight = Color(0xFFFDF8F8)
+val onSurfaceLight = Color(0xFF1C1B1C)
+val surfaceVariantLight = Color(0xFFE4E1E8)
+val onSurfaceVariantLight = Color(0xFF47464B)
+val outlineLight = Color(0xFF77767C)
+val outlineVariantLight = Color(0xFFC8C5CB)
+val scrimLight = Color(0xFF000000)
+val inverseSurfaceLight = Color(0xFF313030)
+val inverseOnSurfaceLight = Color(0xFFF4F0EF)
+val inversePrimaryLight = Color(0xFFBBC3FF)
+val surfaceDimLight = Color(0xFFDDD9D9)
+val surfaceBrightLight = Color(0xFFFDF8F8)
+val surfaceContainerLowestLight = Color(0xFFFFFFFF)
+val surfaceContainerLowLight = Color(0xFFF7F3F2)
+val surfaceContainerLight = Color(0xFFF1EDED)
+val surfaceContainerHighLight = Color(0xFFEBE7E7)
+val surfaceContainerHighestLight = Color(0xFFE5E2E1)
+
+val primaryLightMediumContrast = Color(0xFF525A92)
+val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
+val primaryContainerLightMediumContrast = Color(0xFF293DAE)
+val onPrimaryContainerLightMediumContrast = Color(0xFFE3E4FF)
+val secondaryLightMediumContrast = Color(0xFF2D3553)
+val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightMediumContrast = Color(0xFF646C8D)
+val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryLightMediumContrast = Color(0xFF003080)
+val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightMediumContrast = Color(0xFF0062F3)
+val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
+val errorLightMediumContrast = Color(0xFF730013)
+val onErrorLightMediumContrast = Color(0xFFFFFFFF)
+val errorContainerLightMediumContrast = Color(0xFFD91030)
+val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
+val backgroundLightMediumContrast = Color(0xFFFBF8FF)
+val onBackgroundLightMediumContrast = Color(0xFF1A1B22)
+val surfaceLightMediumContrast = Color(0xFFFDF8F8)
+val onSurfaceLightMediumContrast = Color(0xFF111111)
+val surfaceVariantLightMediumContrast = Color(0xFFE4E1E8)
+val onSurfaceVariantLightMediumContrast = Color(0xFF36363B)
+val outlineLightMediumContrast = Color(0xFF525257)
+val outlineVariantLightMediumContrast = Color(0xFF6D6C72)
+val scrimLightMediumContrast = Color(0xFF000000)
+val inverseSurfaceLightMediumContrast = Color(0xFF313030)
+val inverseOnSurfaceLightMediumContrast = Color(0xFFF4F0EF)
+val inversePrimaryLightMediumContrast = Color(0xFFBBC3FF)
+val surfaceDimLightMediumContrast = Color(0xFFC9C6C5)
+val surfaceBrightLightMediumContrast = Color(0xFFFDF8F8)
+val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightMediumContrast = Color(0xFFF7F3F2)
+val surfaceContainerLightMediumContrast = Color(0xFFEBE7E7)
+val surfaceContainerHighLightMediumContrast = Color(0xFFE0DCDC)
+val surfaceContainerHighestLightMediumContrast = Color(0xFFD4D1D0)
+
+val primaryLightHighContrast = Color(0xFF525A92)
+val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
+val primaryContainerLightHighContrast = Color(0xFF283CAD)
+val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val secondaryLightHighContrast = Color(0xFF222B48)
+val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
+val secondaryContainerLightHighContrast = Color(0xFF404867)
+val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryLightHighContrast = Color(0xFF00276B)
+val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
+val tertiaryContainerLightHighContrast = Color(0xFF0042A8)
+val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
+val errorLightHighContrast = Color(0xFF60000E)
+val onErrorLightHighContrast = Color(0xFFFFFFFF)
+val errorContainerLightHighContrast = Color(0xFF97001C)
+val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
+val backgroundLightHighContrast = Color(0xFFFBF8FF)
+val onBackgroundLightHighContrast = Color(0xFF1A1B22)
+val surfaceLightHighContrast = Color(0xFFFDF8F8)
+val onSurfaceLightHighContrast = Color(0xFF000000)
+val surfaceVariantLightHighContrast = Color(0xFFE4E1E8)
+val onSurfaceVariantLightHighContrast = Color(0xFF000000)
+val outlineLightHighContrast = Color(0xFF2C2C30)
+val outlineVariantLightHighContrast = Color(0xFF49494E)
+val scrimLightHighContrast = Color(0xFF000000)
+val inverseSurfaceLightHighContrast = Color(0xFF313030)
+val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
+val inversePrimaryLightHighContrast = Color(0xFFBBC3FF)
+val surfaceDimLightHighContrast = Color(0xFFBBB8B8)
+val surfaceBrightLightHighContrast = Color(0xFFFDF8F8)
+val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
+val surfaceContainerLowLightHighContrast = Color(0xFFF4F0EF)
+val surfaceContainerLightHighContrast = Color(0xFFE5E2E1)
+val surfaceContainerHighLightHighContrast = Color(0xFFD7D3D3)
+val surfaceContainerHighestLightHighContrast = Color(0xFFC9C6C5)
+
+val primaryDark = Color(0xFFBBC3FF)
+val onPrimaryDark = Color(0xFF001D93)
+val primaryContainerDark = Color(0xFF293DAE)
+val onPrimaryContainerDark = Color(0xFFABB5FF)
+val secondaryDark = Color(0xFFBDC5EA)
+val onSecondaryDark = Color(0xFF272F4D)
+val secondaryContainerDark = Color(0xFF8890B3)
+val onSecondaryContainerDark = Color(0xFF212946)
+val tertiaryDark = Color(0xFFB2C5FF)
+val onTertiaryDark = Color(0xFF002B74)
+val tertiaryContainerDark = Color(0xFF0468FF)
+val onTertiaryContainerDark = Color(0xFFFBF9FF)
+val errorDark = Color(0xFFFFB3B0)
+val onErrorDark = Color(0xFF680010)
+val errorContainerDark = Color(0xFFFF5359)
+val onErrorContainerDark = Color(0xFF220002)
+val backgroundDark = Color(0xFF12131A)
+val onBackgroundDark = Color(0xFFE3E1EB)
+val surfaceDark = Color(0xFF141313)
+val onSurfaceDark = Color(0xFFE5E2E1)
+val surfaceVariantDark = Color(0xFF47464B)
+val onSurfaceVariantDark = Color(0xFFC8C5CB)
+val outlineDark = Color(0xFF919096)
+val outlineVariantDark = Color(0xFF47464B)
+val scrimDark = Color(0xFF000000)
+val inverseSurfaceDark = Color(0xFFE5E2E1)
+val inverseOnSurfaceDark = Color(0xFF313030)
+val inversePrimaryDark = Color(0xFF4053C3)
+val surfaceDimDark = Color(0xFF141313)
+val surfaceBrightDark = Color(0xFF3A3939)
+val surfaceContainerLowestDark = Color(0xFF0E0E0E)
+val surfaceContainerLowDark = Color(0xFF1C1B1C)
+val surfaceContainerDark = Color(0xFF201F20)
+val surfaceContainerHighDark = Color(0xFF2B2A2A)
+val surfaceContainerHighestDark = Color(0xFF353435)
+
+val primaryDarkMediumContrast = Color(0xFFBBC3FF)
+val onPrimaryDarkMediumContrast = Color(0xFF001677)
+val primaryContainerDarkMediumContrast = Color(0xFF7587FA)
+val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
+val secondaryDarkMediumContrast = Color(0xFFD4DBFF)
+val onSecondaryDarkMediumContrast = Color(0xFF1C2441)
+val secondaryContainerDarkMediumContrast = Color(0xFF8890B3)
+val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
+val tertiaryDarkMediumContrast = Color(0xFFD2DBFF)
+val onTertiaryDarkMediumContrast = Color(0xFF00215E)
+val tertiaryContainerDarkMediumContrast = Color(0xFF5D8BFF)
+val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
+val errorDarkMediumContrast = Color(0xFFFFD2CF)
+val onErrorDarkMediumContrast = Color(0xFF54000B)
+val errorContainerDarkMediumContrast = Color(0xFFFF5359)
+val onErrorContainerDarkMediumContrast = Color(0xFF000000)
+val backgroundDarkMediumContrast = Color(0xFF12131A)
+val onBackgroundDarkMediumContrast = Color(0xFFE3E1EB)
+val surfaceDarkMediumContrast = Color(0xFF141313)
+val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkMediumContrast = Color(0xFF47464B)
+val onSurfaceVariantDarkMediumContrast = Color(0xFFDEDBE1)
+val outlineDarkMediumContrast = Color(0xFFB3B1B7)
+val outlineVariantDarkMediumContrast = Color(0xFF918F95)
+val scrimDarkMediumContrast = Color(0xFF000000)
+val inverseSurfaceDarkMediumContrast = Color(0xFFE5E2E1)
+val inverseOnSurfaceDarkMediumContrast = Color(0xFF2B2A2A)
+val inversePrimaryDarkMediumContrast = Color(0xFF263AAC)
+val surfaceDimDarkMediumContrast = Color(0xFF141313)
+val surfaceBrightDarkMediumContrast = Color(0xFF454444)
+val surfaceContainerLowestDarkMediumContrast = Color(0xFF080707)
+val surfaceContainerLowDarkMediumContrast = Color(0xFF1E1D1E)
+val surfaceContainerDarkMediumContrast = Color(0xFF282828)
+val surfaceContainerHighDarkMediumContrast = Color(0xFF333232)
+val surfaceContainerHighestDarkMediumContrast = Color(0xFF3E3D3D)
+
+val primaryDarkHighContrast = Color(0xFFBBC3FF)
+val onPrimaryDarkHighContrast = Color(0xFF000000)
+val primaryContainerDarkHighContrast = Color(0xFFB6BFFF)
+val onPrimaryContainerDarkHighContrast = Color(0xFF000533)
+val secondaryDarkHighContrast = Color(0xFFEEEFFF)
+val onSecondaryDarkHighContrast = Color(0xFF000000)
+val secondaryContainerDarkHighContrast = Color(0xFFB9C1E6)
+val onSecondaryContainerDarkHighContrast = Color(0xFF020926)
+val tertiaryDarkHighContrast = Color(0xFFEDEFFF)
+val onTertiaryDarkHighContrast = Color(0xFF000000)
+val tertiaryContainerDarkHighContrast = Color(0xFFADC1FF)
+val onTertiaryContainerDarkHighContrast = Color(0xFF000926)
+val errorDarkHighContrast = Color(0xFFFFECEA)
+val onErrorDarkHighContrast = Color(0xFF000000)
+val errorContainerDarkHighContrast = Color(0xFFFFADAB)
+val onErrorContainerDarkHighContrast = Color(0xFF220002)
+val backgroundDarkHighContrast = Color(0xFF12131A)
+val onBackgroundDarkHighContrast = Color(0xFFE3E1EB)
+val surfaceDarkHighContrast = Color(0xFF141313)
+val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
+val surfaceVariantDarkHighContrast = Color(0xFF47464B)
+val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF)
+val outlineDarkHighContrast = Color(0xFFF2EFF5)
+val outlineVariantDarkHighContrast = Color(0xFFC4C2C8)
+val scrimDarkHighContrast = Color(0xFF000000)
+val inverseSurfaceDarkHighContrast = Color(0xFFE5E2E1)
+val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
+val inversePrimaryDarkHighContrast = Color(0xFF263AAC)
+val surfaceDimDarkHighContrast = Color(0xFF141313)
+val surfaceBrightDarkHighContrast = Color(0xFF515050)
+val surfaceContainerLowestDarkHighContrast = Color(0xFF000000)
+val surfaceContainerLowDarkHighContrast = Color(0xFF201F20)
+val surfaceContainerDarkHighContrast = Color(0xFF313030)
+val surfaceContainerHighDarkHighContrast = Color(0xFF3C3B3B)
+val surfaceContainerHighestDarkHighContrast = Color(0xFF484646)
+
+val warningLight = Color(0xFF765B0B)
+val onWarningLight = Color(0xFFFFFFFF)
+val warningContainerLight = Color(0xFFFFDF97)
+val onWarningContainerLight = Color(0xFF5A4300)
+
+val warningLightMediumContrast = Color(0xFF453400)
+val onWarningLightMediumContrast = Color(0xFFFFFFFF)
+val warningContainerLightMediumContrast = Color(0xFF86691C)
+val onWarningContainerLightMediumContrast = Color(0xFFFFFFFF)
+
+val warningLightHighContrast = Color(0xFF392A00)
+val onWarningLightHighContrast = Color(0xFFFFFFFF)
+val warningContainerLightHighContrast = Color(0xFF5D4600)
+val onWarningContainerLightHighContrast = Color(0xFFFFFFFF)
+
+val warningDark = Color(0xFFE6C26C)
+val onWarningDark = Color(0xFF3E2E00)
+val warningContainerDark = Color(0xFF5A4300)
+val onWarningContainerDark = Color(0xFFFFDF97)
+
+val warningDarkMediumContrast = Color(0xFFFED87F)
+val onWarningDarkMediumContrast = Color(0xFF312400)
+val warningContainerDarkMediumContrast = Color(0xFFAD8D3D)
+val onWarningContainerDarkMediumContrast = Color(0xFF000000)
+
+val warningDarkHighContrast = Color(0xFFFFEECF)
+val onWarningDarkHighContrast = Color(0xFF000000)
+val warningContainerDarkHighContrast = Color(0xFFE2BE69)
+val onWarningContainerDarkHighContrast = Color(0xFF110A00)
+
diff --git a/app/src/processing/app/ui/theme/m3/Theme.kt b/app/src/processing/app/ui/theme/m3/Theme.kt
new file mode 100644
index 0000000000..d1b2f403a8
--- /dev/null
+++ b/app/src/processing/app/ui/theme/m3/Theme.kt
@@ -0,0 +1,301 @@
+
+
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.graphics.Color
+
+@Immutable
+data class ExtendedColorScheme(
+ val warning: ColorFamily,
+)
+
+val lightScheme = lightColorScheme(
+ primary = primaryLight,
+ onPrimary = onPrimaryLight,
+ primaryContainer = primaryContainerLight,
+ onPrimaryContainer = onPrimaryContainerLight,
+ secondary = secondaryLight,
+ onSecondary = onSecondaryLight,
+ secondaryContainer = secondaryContainerLight,
+ onSecondaryContainer = onSecondaryContainerLight,
+ tertiary = tertiaryLight,
+ onTertiary = onTertiaryLight,
+ tertiaryContainer = tertiaryContainerLight,
+ onTertiaryContainer = onTertiaryContainerLight,
+ error = errorLight,
+ onError = onErrorLight,
+ errorContainer = errorContainerLight,
+ onErrorContainer = onErrorContainerLight,
+ background = backgroundLight,
+ onBackground = onBackgroundLight,
+ surface = surfaceLight,
+ onSurface = onSurfaceLight,
+ surfaceVariant = surfaceVariantLight,
+ onSurfaceVariant = onSurfaceVariantLight,
+ outline = outlineLight,
+ outlineVariant = outlineVariantLight,
+ scrim = scrimLight,
+ inverseSurface = inverseSurfaceLight,
+ inverseOnSurface = inverseOnSurfaceLight,
+ inversePrimary = inversePrimaryLight,
+ surfaceDim = surfaceDimLight,
+ surfaceBright = surfaceBrightLight,
+ surfaceContainerLowest = surfaceContainerLowestLight,
+ surfaceContainerLow = surfaceContainerLowLight,
+ surfaceContainer = surfaceContainerLight,
+ surfaceContainerHigh = surfaceContainerHighLight,
+ surfaceContainerHighest = surfaceContainerHighestLight,
+)
+
+val darkScheme = darkColorScheme(
+ primary = primaryDark,
+ onPrimary = onPrimaryDark,
+ primaryContainer = primaryContainerDark,
+ onPrimaryContainer = onPrimaryContainerDark,
+ secondary = secondaryDark,
+ onSecondary = onSecondaryDark,
+ secondaryContainer = secondaryContainerDark,
+ onSecondaryContainer = onSecondaryContainerDark,
+ tertiary = tertiaryDark,
+ onTertiary = onTertiaryDark,
+ tertiaryContainer = tertiaryContainerDark,
+ onTertiaryContainer = onTertiaryContainerDark,
+ error = errorDark,
+ onError = onErrorDark,
+ errorContainer = errorContainerDark,
+ onErrorContainer = onErrorContainerDark,
+ background = backgroundDark,
+ onBackground = onBackgroundDark,
+ surface = surfaceDark,
+ onSurface = onSurfaceDark,
+ surfaceVariant = surfaceVariantDark,
+ onSurfaceVariant = onSurfaceVariantDark,
+ outline = outlineDark,
+ outlineVariant = outlineVariantDark,
+ scrim = scrimDark,
+ inverseSurface = inverseSurfaceDark,
+ inverseOnSurface = inverseOnSurfaceDark,
+ inversePrimary = inversePrimaryDark,
+ surfaceDim = surfaceDimDark,
+ surfaceBright = surfaceBrightDark,
+ surfaceContainerLowest = surfaceContainerLowestDark,
+ surfaceContainerLow = surfaceContainerLowDark,
+ surfaceContainer = surfaceContainerDark,
+ surfaceContainerHigh = surfaceContainerHighDark,
+ surfaceContainerHighest = surfaceContainerHighestDark,
+)
+
+private val mediumContrastLightColorScheme = lightColorScheme(
+ primary = primaryLightMediumContrast,
+ onPrimary = onPrimaryLightMediumContrast,
+ primaryContainer = primaryContainerLightMediumContrast,
+ onPrimaryContainer = onPrimaryContainerLightMediumContrast,
+ secondary = secondaryLightMediumContrast,
+ onSecondary = onSecondaryLightMediumContrast,
+ secondaryContainer = secondaryContainerLightMediumContrast,
+ onSecondaryContainer = onSecondaryContainerLightMediumContrast,
+ tertiary = tertiaryLightMediumContrast,
+ onTertiary = onTertiaryLightMediumContrast,
+ tertiaryContainer = tertiaryContainerLightMediumContrast,
+ onTertiaryContainer = onTertiaryContainerLightMediumContrast,
+ error = errorLightMediumContrast,
+ onError = onErrorLightMediumContrast,
+ errorContainer = errorContainerLightMediumContrast,
+ onErrorContainer = onErrorContainerLightMediumContrast,
+ background = backgroundLightMediumContrast,
+ onBackground = onBackgroundLightMediumContrast,
+ surface = surfaceLightMediumContrast,
+ onSurface = onSurfaceLightMediumContrast,
+ surfaceVariant = surfaceVariantLightMediumContrast,
+ onSurfaceVariant = onSurfaceVariantLightMediumContrast,
+ outline = outlineLightMediumContrast,
+ outlineVariant = outlineVariantLightMediumContrast,
+ scrim = scrimLightMediumContrast,
+ inverseSurface = inverseSurfaceLightMediumContrast,
+ inverseOnSurface = inverseOnSurfaceLightMediumContrast,
+ inversePrimary = inversePrimaryLightMediumContrast,
+ surfaceDim = surfaceDimLightMediumContrast,
+ surfaceBright = surfaceBrightLightMediumContrast,
+ surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
+ surfaceContainerLow = surfaceContainerLowLightMediumContrast,
+ surfaceContainer = surfaceContainerLightMediumContrast,
+ surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
+ surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
+)
+
+private val highContrastLightColorScheme = lightColorScheme(
+ primary = primaryLightHighContrast,
+ onPrimary = onPrimaryLightHighContrast,
+ primaryContainer = primaryContainerLightHighContrast,
+ onPrimaryContainer = onPrimaryContainerLightHighContrast,
+ secondary = secondaryLightHighContrast,
+ onSecondary = onSecondaryLightHighContrast,
+ secondaryContainer = secondaryContainerLightHighContrast,
+ onSecondaryContainer = onSecondaryContainerLightHighContrast,
+ tertiary = tertiaryLightHighContrast,
+ onTertiary = onTertiaryLightHighContrast,
+ tertiaryContainer = tertiaryContainerLightHighContrast,
+ onTertiaryContainer = onTertiaryContainerLightHighContrast,
+ error = errorLightHighContrast,
+ onError = onErrorLightHighContrast,
+ errorContainer = errorContainerLightHighContrast,
+ onErrorContainer = onErrorContainerLightHighContrast,
+ background = backgroundLightHighContrast,
+ onBackground = onBackgroundLightHighContrast,
+ surface = surfaceLightHighContrast,
+ onSurface = onSurfaceLightHighContrast,
+ surfaceVariant = surfaceVariantLightHighContrast,
+ onSurfaceVariant = onSurfaceVariantLightHighContrast,
+ outline = outlineLightHighContrast,
+ outlineVariant = outlineVariantLightHighContrast,
+ scrim = scrimLightHighContrast,
+ inverseSurface = inverseSurfaceLightHighContrast,
+ inverseOnSurface = inverseOnSurfaceLightHighContrast,
+ inversePrimary = inversePrimaryLightHighContrast,
+ surfaceDim = surfaceDimLightHighContrast,
+ surfaceBright = surfaceBrightLightHighContrast,
+ surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
+ surfaceContainerLow = surfaceContainerLowLightHighContrast,
+ surfaceContainer = surfaceContainerLightHighContrast,
+ surfaceContainerHigh = surfaceContainerHighLightHighContrast,
+ surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
+)
+
+private val mediumContrastDarkColorScheme = darkColorScheme(
+ primary = primaryDarkMediumContrast,
+ onPrimary = onPrimaryDarkMediumContrast,
+ primaryContainer = primaryContainerDarkMediumContrast,
+ onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
+ secondary = secondaryDarkMediumContrast,
+ onSecondary = onSecondaryDarkMediumContrast,
+ secondaryContainer = secondaryContainerDarkMediumContrast,
+ onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
+ tertiary = tertiaryDarkMediumContrast,
+ onTertiary = onTertiaryDarkMediumContrast,
+ tertiaryContainer = tertiaryContainerDarkMediumContrast,
+ onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
+ error = errorDarkMediumContrast,
+ onError = onErrorDarkMediumContrast,
+ errorContainer = errorContainerDarkMediumContrast,
+ onErrorContainer = onErrorContainerDarkMediumContrast,
+ background = backgroundDarkMediumContrast,
+ onBackground = onBackgroundDarkMediumContrast,
+ surface = surfaceDarkMediumContrast,
+ onSurface = onSurfaceDarkMediumContrast,
+ surfaceVariant = surfaceVariantDarkMediumContrast,
+ onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
+ outline = outlineDarkMediumContrast,
+ outlineVariant = outlineVariantDarkMediumContrast,
+ scrim = scrimDarkMediumContrast,
+ inverseSurface = inverseSurfaceDarkMediumContrast,
+ inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
+ inversePrimary = inversePrimaryDarkMediumContrast,
+ surfaceDim = surfaceDimDarkMediumContrast,
+ surfaceBright = surfaceBrightDarkMediumContrast,
+ surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
+ surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
+ surfaceContainer = surfaceContainerDarkMediumContrast,
+ surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
+ surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
+)
+
+private val highContrastDarkColorScheme = darkColorScheme(
+ primary = primaryDarkHighContrast,
+ onPrimary = onPrimaryDarkHighContrast,
+ primaryContainer = primaryContainerDarkHighContrast,
+ onPrimaryContainer = onPrimaryContainerDarkHighContrast,
+ secondary = secondaryDarkHighContrast,
+ onSecondary = onSecondaryDarkHighContrast,
+ secondaryContainer = secondaryContainerDarkHighContrast,
+ onSecondaryContainer = onSecondaryContainerDarkHighContrast,
+ tertiary = tertiaryDarkHighContrast,
+ onTertiary = onTertiaryDarkHighContrast,
+ tertiaryContainer = tertiaryContainerDarkHighContrast,
+ onTertiaryContainer = onTertiaryContainerDarkHighContrast,
+ error = errorDarkHighContrast,
+ onError = onErrorDarkHighContrast,
+ errorContainer = errorContainerDarkHighContrast,
+ onErrorContainer = onErrorContainerDarkHighContrast,
+ background = backgroundDarkHighContrast,
+ onBackground = onBackgroundDarkHighContrast,
+ surface = surfaceDarkHighContrast,
+ onSurface = onSurfaceDarkHighContrast,
+ surfaceVariant = surfaceVariantDarkHighContrast,
+ onSurfaceVariant = onSurfaceVariantDarkHighContrast,
+ outline = outlineDarkHighContrast,
+ outlineVariant = outlineVariantDarkHighContrast,
+ scrim = scrimDarkHighContrast,
+ inverseSurface = inverseSurfaceDarkHighContrast,
+ inverseOnSurface = inverseOnSurfaceDarkHighContrast,
+ inversePrimary = inversePrimaryDarkHighContrast,
+ surfaceDim = surfaceDimDarkHighContrast,
+ surfaceBright = surfaceBrightDarkHighContrast,
+ surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
+ surfaceContainerLow = surfaceContainerLowDarkHighContrast,
+ surfaceContainer = surfaceContainerDarkHighContrast,
+ surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
+ surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
+)
+
+val extendedLight = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningLight,
+ onWarningLight,
+ warningContainerLight,
+ onWarningContainerLight,
+ ),
+)
+
+val extendedDark = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningDark,
+ onWarningDark,
+ warningContainerDark,
+ onWarningContainerDark,
+ ),
+)
+
+val extendedLightMediumContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningLightMediumContrast,
+ onWarningLightMediumContrast,
+ warningContainerLightMediumContrast,
+ onWarningContainerLightMediumContrast,
+ ),
+)
+
+val extendedLightHighContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningLightHighContrast,
+ onWarningLightHighContrast,
+ warningContainerLightHighContrast,
+ onWarningContainerLightHighContrast,
+ ),
+)
+
+val extendedDarkMediumContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningDarkMediumContrast,
+ onWarningDarkMediumContrast,
+ warningContainerDarkMediumContrast,
+ onWarningContainerDarkMediumContrast,
+ ),
+)
+
+val extendedDarkHighContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningDarkHighContrast,
+ onWarningDarkHighContrast,
+ warningContainerDarkHighContrast,
+ onWarningContainerDarkHighContrast,
+ ),
+)
+
+@Immutable
+data class ColorFamily(
+ val color: Color,
+ val onColor: Color,
+ val colorContainer: Color,
+ val onColorContainer: Color
+)
\ No newline at end of file
diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties
index 19a5c9f866..8001796f59 100644
--- a/build/shared/lib/languages/PDE.properties
+++ b/build/shared/lib/languages/PDE.properties
@@ -640,6 +640,22 @@ beta.button = Ok
color_chooser = Color Selector
color_chooser.select = Select
+
+# ---------------------------------------
+# Welcome Screen
+welcome.processing.logo = Processing Logo
+welcome.processing.title = Welcome to Processing
+welcome.actions.sketch.new = Empty Sketch
+welcome.actions.examples = Open Examples
+welcome.actions.show_startup = Show this window at startup
+welcome.resources.title = Resources
+welcome.resources.get_started = Get Started
+welcome.resources.tutorials = Tutorials
+welcome.resources.documentation = Reference
+welcome.community.title = Join our community
+welcome.community.forum = Forum
+welcome.sketch.open = Open
+
# ---------------------------------------
# Movie Maker
diff --git a/build/shared/lib/languages/PDE_nl.properties b/build/shared/lib/languages/PDE_nl.properties
index e7f11b0a1f..76865397b3 100644
--- a/build/shared/lib/languages/PDE_nl.properties
+++ b/build/shared/lib/languages/PDE_nl.properties
@@ -322,6 +322,22 @@ beta.title = Dankuwel voor het testen van deze Processing Beta!
beta.message = Deze preview release laat ons feedback verzamelen en problemen oplossen. **Sommige functies werken mogelijk niet zoals verwacht.** Als u problemen ondervindt, [post dan op het forum](https://discourse.processing.org) of [open een GitHub issue](https://github.com/processing/processing4/issues).
beta.button = Ok
+
+# ---------------------------------------
+# Welcome Screen
+welcome.processing.logo = Processing Logo
+welcome.processing.title = Welkom bij Processing!
+welcome.actions.sketch.new = Nieuwe Schets
+welcome.actions.examples = Open Voorbeelden
+welcome.actions.show_startup = Laat dit scherm zien bij opstarten
+welcome.resources.title = Resources
+welcome.resources.video = Video Cursus
+welcome.resources.get_started = Om te beginnen
+welcome.resources.tutorials = Tutorials
+welcome.resources.documentation = Handleiding
+welcome.community.title = Neem deel aan de Community
+welcome.community.forum = Forum
+
# ---------------------------------------
# Color Chooser
color_chooser = Kies een kleur...
diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java
index 3fab2c8b17..7ce9e45be7 100644
--- a/java/src/processing/mode/java/JavaEditor.java
+++ b/java/src/processing/mode/java/JavaEditor.java
@@ -288,13 +288,7 @@ public JMenu buildHelpMenu() {
item = new JMenuItem(Language.text("menu.help.welcome"));
item.addActionListener(e -> {
- try {
- new Welcome(base);
- } catch (IOException ioe) {
- Messages.showWarning("Unwelcome Error",
- "Please report this error to\n" +
- "https://github.com/processing/processing4/issues", ioe);
- }
+ PDEWelcomeKt.showWelcomeScreen(base);
});
menu.add(item);