Initial commit

This commit is contained in:
Pyrex 2023-07-01 20:55:45 -07:00
commit 3776e85bc0
17 changed files with 661 additions and 0 deletions

40
.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
*.kt.old
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

7
.idea/encodings.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/kotlin" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.10" />
</component>
</project>

13
.idea/misc.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

115
.idea/workspace.xml Normal file
View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7c866fd4-dd39-4ede-9dcf-e623b1d0f46d" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/encodings.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/kotlinc.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/AppModel.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/AppView.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/ButtonArt.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/ImageButton.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/Main.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/TodoView.kt.old" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/Util.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/resources/Main.kt.old" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/resources/demoIcon.png" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Kotlin File" />
<option value="Class" />
<option value="Kotlin Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2Rx2CD5ArGjpQZZIPM7XNqEj2yY" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Nyeogit/taskzap/taskzap/src/main/resources&quot;
}
}</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Nyeogit\taskzap\taskzap\src\main\resources" />
<recent name="C:\Nyeogit\taskzap\taskzap\src\main\kotlin" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Nyeogit\taskzap\taskzap\src\main\resources" />
</key>
</component>
<component name="RunManager">
<configuration name="MainKt" type="JetRunConfigurationType" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="MainKt" />
<module name="taskzap" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="true" type="JetRunConfigurationType">
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
<option name="filePath" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Kotlin.MainKt" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="7c866fd4-dd39-4ede-9dcf-e623b1d0f46d" name="Changes" comment="" />
<created>1688177283964</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1688177283964</updated>
</task>
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
</project>

104
pom.xml Normal file
View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>taskzap</artifactId>
<groupId>app.chromaticdragon</groupId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>consoleApp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
</properties>
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>1.7.10</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>1.7.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-datetime-jvm</artifactId>
<version>0.4.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,53 @@
import kotlinx.datetime.LocalDateTime
import java.awt.image.BufferedImage
import java.util.*
import kotlin.collections.HashMap
class AppModel {
var version: Long = 0
private set
private var tasks: HashMap<UUID, TaskRecord> = HashMap()
fun updateTask(task: TaskRecord): TaskRecord? { // returns the old version
version += 1
val old = tasks[task.uuid]
tasks[task.uuid] = task
return old
}
fun getTask(uuid: UUID): TaskRecord? {
return tasks[uuid]
}
fun removeTask(uuid: UUID): TaskRecord? {
version += 1
return tasks.remove(uuid)
}
fun getTasksByBlinkAt(): ArrayList<TaskRecord> {
val list = ArrayList<TaskRecord>()
for (t in tasks) { list.add(t.value) }
list.sortBy { t -> t.blinkAt }
return list
}
}
class TaskRecord(
val uuid: UUID,
val icon: IconRecord,
val task: String,
val link: LinkRecord?,
val createdAt: LocalDateTime,
val blinkAt: LocalDateTime,
)
class LinkRecord(
val icon: IconRecord,
val window: Int,
val pid: Int,
val filename: String,
)
class IconRecord(val image: BufferedImage) {
val cachedButtonArt = ButtonArt.fromArbitraryImage(image) // don't persist this, just have it always ready
}

125
src/main/kotlin/AppView.kt Normal file
View File

@ -0,0 +1,125 @@
import java.awt.*
import javax.swing.BoxLayout
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.border.EmptyBorder
private val GRID_SIZE = 64;
private val GRID_SIZE_INNER = 56;
private val GRID_INSET = (GRID_SIZE - GRID_SIZE_INNER) / 2
class AppView(model: AppModel): JDialog() {
private val viewModel = AppViewModel(model)
private val controlPanel = buildControlPanel()
private val iconPanel = JPanel()
// TODO: Replace these with icons that have clear meaning
init {
isAlwaysOnTop = true
isUndecorated = true
background = TRANSPARENT
bounds = Rectangle(0, 0, Toolkit.getDefaultToolkit().screenSize.width, 64)
contentPane.layout = BorderLayout()
contentPane.add(controlPanel, BorderLayout.WEST)
contentPane.add(iconPanel, BorderLayout.CENTER)
iconPanel.layout = FlowLayout(FlowLayout.LEFT, 0, 0)
controlPanel.background = TRANSPARENT;
iconPanel.background = TRANSPARENT;
}
fun touch() {
viewModel.ifDirty(contentPane) {
val ipHeight = iconPanel.height
val iconSize = ipHeight * 3/4;
val meterHeight = ipHeight - iconSize;
iconPanel.removeAll()
val tasks = viewModel.model.getTasksByBlinkAt()
for (task in tasks) {
iconPanel.add(TaskView(task))
}
}
}
private fun buildControlPanel(): JPanel {
val moveButton = ImageButton(
ButtonArt(
getImage("/move_unlit.png")!!,
getImage("/move_lit.png")!!,
getImage("/move_pressed.png")!!,
),
null
)
// TODO: Do something
moveButton.preferredSize = Dimension(GRID_SIZE, GRID_SIZE)
moveButton.border = EmptyBorder(GRID_INSET, GRID_INSET, GRID_INSET, GRID_INSET)
moveButton.callback = { print("test") }
val controlPanel = JPanel()
controlPanel.layout = BoxLayout(controlPanel, BoxLayout.PAGE_AXIS)
controlPanel.add(moveButton)
return controlPanel
}
}
// for now: just reexpose the underlying model
// we don't have a layer of state beyond what can be kept in the UI
class AppViewModel(val model: AppModel) {
private var cpHeight: Int = -1
private var version: Long = -1
fun ifDirty(contentPane: Container, body: () -> Unit) {
if (version != model.version || contentPane.height != cpHeight) {
body()
cpHeight = contentPane.height
version = model.version
}
}
}
class TaskView(
private val task: TaskRecord,
): JPanel() {
private var button: ImageButton
init {
layout = BorderLayout()
preferredSize = Dimension(GRID_SIZE, GRID_SIZE)
border = EmptyBorder(GRID_INSET, GRID_INSET, GRID_INSET, GRID_INSET)
background = TRANSPARENT
button = ImageButton(
task.icon.cachedButtonArt,
null
)
add(button)
}
/*
override fun paintComponent(gnull: Graphics?) {
super.paintComponent(gnull)
val g = gnull!!
// icon occupies top of space
g.drawImage(task.icon.image, 0, 0, iconSize, iconSize, Color(0, 0, 0, 0)) { _, _, _, _, _, _ -> false }
// bar occupies middle 1/2 of space
val realBarHeight = meterHeight / 2
val barY = iconSize + (meterHeight - realBarHeight)/2
val arcSize = realBarHeight / 2
val barX = arcSize
val realBarWidth = iconSize - arcSize * 2
g.color = Color(0, 255, 0, 255)
g.fillRoundRect(barX, barY, realBarWidth, realBarHeight, arcSize, arcSize)
}
*/
}

View File

@ -0,0 +1,42 @@
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.awt.image.RescaleOp
// val MAX_SIZE: Int = 256
// TODO: Enforce MAX_SIZE
// TODO: Cookie cutter out a roundrect
class ButtonArt(
val base: BufferedImage,
val highlighted: BufferedImage,
val pressed: BufferedImage
) {
companion object {
fun fromArbitraryImage(base: BufferedImage): ButtonArt {
return ButtonArt(
base,
base.toHighlighted(),
base.toPressed(),
)
}
}
}
private fun BufferedImage.toHighlighted(): BufferedImage {
val highlighted = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
val g = highlighted.graphics as Graphics2D
val op = RescaleOp(0.75f, 128f, null)
g.drawImage(this, 0, 0, width, height, 0, 0, width, height, null)
op.filter(highlighted, highlighted)
return highlighted
}
private fun BufferedImage.toPressed(): BufferedImage {
val pressed = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
val g = pressed.graphics as Graphics2D
val op = RescaleOp(2.0f, 0.0f, null)
g.drawImage(this, 0, 0, width, height, 0, 0, width, height, null)
op.filter(pressed, pressed)
return pressed
}

View File

@ -0,0 +1,63 @@
import java.awt.Dimension
import java.awt.Graphics
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.image.BufferedImage
import javax.swing.JPanel
class ImageButton(
private val art: ButtonArt,
var callback: (() -> Unit)?,
): JPanel() {
private var mouseOver = false
private var mouseDown = false
init {
background = TRANSPARENT
preferredSize = Dimension(art.base.width, art.base.height)
this.addMouseListener(object: MouseAdapter() {
override fun mouseEntered(e: MouseEvent?) {
super.mouseEntered(e)
mouseOver = true
repaint()
}
override fun mouseExited(e: MouseEvent?) {
super.mouseExited(e)
mouseOver = false
repaint()
}
override fun mousePressed(e: MouseEvent?) {
super.mousePressed(e)
mouseDown = true
repaint()
}
override fun mouseReleased(e: MouseEvent?) {
super.mouseReleased(e)
mouseDown = false
repaint()
}
override fun mouseClicked(e: MouseEvent?) {
super.mouseClicked(e)
callback?.invoke()
}
})
}
override fun paintComponent(gnull: Graphics?) {
super.paintComponent(gnull)
val g = gnull!!
val img = if (mouseDown) { art.pressed } else if (mouseOver) { art.highlighted } else { art.base }
val insets = insets
val dx = width - (insets.left + insets.right)
val dy = height - (insets.top + insets.bottom)
g.drawImage(img, insets.left, insets.top, dx, dy, null)
}
}

71
src/main/kotlin/Main.kt Normal file
View File

@ -0,0 +1,71 @@
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.awt.image.BufferedImage
import javax.swing.JFrame
import javax.swing.Timer
import kotlin.system.exitProcess
import kotlinx.datetime.*
import kotlinx.datetime.TimeZone
import java.awt.*
import java.io.ByteArrayInputStream
import java.util.*
import javax.imageio.ImageIO
import javax.swing.JButton
import javax.swing.JLabel
import javax.swing.JPanel
fun main(args: Array<String>) {
val app = App()
app.start()
}
class App {
private var model = AppModel()
private var running: Boolean = false
private val worker = createWorker()
private val appView = createAppView()
fun start() {
running = true
worker.start()
val demoIcon = getImage("/demoIcon.png")!!
for (i in 0..2) {
model.updateTask(
TaskRecord(
uuid = UUID.randomUUID(),
icon = IconRecord(demoIcon),
task = "FIX THE DOG",
link = null,
createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()),
blinkAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
)
)
}
}
private fun createWorker(): Timer {
return Timer(33) { onFrame() }
}
private fun createAppView(): AppView {
val av = AppView(model)
av.addWindowListener(object: WindowAdapter() {
override fun windowClosing(e: WindowEvent?) {
running = false
}
})
return av
}
private fun onFrame() {
if (!running) { exitProcess(0) }
if (running) appView.isVisible = true
appView.touch()
}
}

16
src/main/kotlin/Util.kt Normal file
View File

@ -0,0 +1,16 @@
import java.awt.AlphaComposite
import java.awt.Color
import java.awt.Composite
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.awt.image.RescaleOp
import java.io.ByteArrayInputStream
import java.net.URL
import javax.imageio.ImageIO
val TRANSPARENT: Color = Color(0, 0, 0, 0)
fun ByteArray.toImage(): BufferedImage = ImageIO.read(ByteArrayInputStream(this))
fun getImage(name: String): BufferedImage? = getResource(name)?.readBytes()?.toImage()
fun getResource(name: String): URL? = App::class.java.getResource(name)

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB