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);